1 /*
2  * tkMenubutton.c --
3  *
4  *	This module implements button-like widgets that are used
5  *	to invoke pull-down menus.
6  *
7  * Copyright (c) 1990-1994 The Regents of the University of California.
8  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
9  *
10  * See the file "license.terms" for information on usage and redistribution
11  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  *
13  * SCCS: @(#) tkMenubutton.c 1.77 96/02/15 18:52:22
14  */
15 
16 #include "tkInt.h"
17 #include "tkDefault.h"
18 
19 /*
20  * A data structure of the following type is kept for each
21  * widget managed by this file:
22  */
23 
24 typedef struct {
25     Tk_Window tkwin;		/* Window that embodies the widget.  NULL
26 				 * means that the window has been destroyed
27 				 * but the data structures haven't yet been
28 				 * cleaned up.*/
29     Display *display;		/* Display containing widget.  Needed, among
30 				 * other things, so that resources can bee
31 				 * freed up even after tkwin has gone away. */
32     Tcl_Interp *interp;		/* Interpreter associated with menubutton. */
33     Tcl_Command widgetCmd;	/* Token for menubutton's widget command. */
34     char *menuName;		/* Name of menu associated with widget.
35 				 * Malloc-ed. */
36 
37     /*
38      * Information about what's displayed in the menu button:
39      */
40 
41     char *text;			/* Text to display in button (malloc'ed)
42 				 * or NULL. */
43     int numChars;		/* # of characters in text. */
44     int underline;		/* Index of character to underline. */
45     char *textVarName;		/* Name of variable (malloc'ed) or NULL.
46 				 * If non-NULL, button displays the contents
47 				 * of this variable. */
48     Pixmap bitmap;		/* Bitmap to display or None.  If not None
49 				 * then text and textVar and underline
50 				 * are ignored. */
51     char *imageString;		/* Name of image to display (malloc'ed), or
52 				 * NULL.  If non-NULL, bitmap, text, and
53 				 * textVarName are ignored. */
54     Tk_Image image;		/* Image to display in window, or NULL if
55 				 * none. */
56 
57     /*
58      * Information used when displaying widget:
59      */
60 
61     Tk_Uid state;		/* State of button for display purposes:
62 				 * normal, active, or disabled. */
63     Tk_3DBorder normalBorder;	/* Structure used to draw 3-D
64 				 * border and background when window
65 				 * isn't active.  NULL means no such
66 				 * border exists. */
67     Tk_3DBorder activeBorder;	/* Structure used to draw 3-D
68 				 * border and background when window
69 				 * is active.  NULL means no such
70 				 * border exists. */
71     int borderWidth;		/* Width of border. */
72     int relief;			/* 3-d effect: TK_RELIEF_RAISED, etc. */
73     int highlightWidth;		/* Width in pixels of highlight to draw
74 				 * around widget when it has the focus.
75 				 * <= 0 means don't draw a highlight. */
76     XColor *highlightBgColorPtr;
77 				/* Color for drawing traversal highlight
78 				 * area when highlight is off. */
79     XColor *highlightColorPtr;	/* Color for drawing traversal highlight. */
80     int inset;			/* Total width of all borders, including
81 				 * traversal highlight and 3-D border.
82 				 * Indicates how much interior stuff must
83 				 * be offset from outside edges to leave
84 				 * room for borders. */
85     XFontStruct *fontPtr;	/* Information about text font, or NULL. */
86     XColor *normalFg;		/* Foreground color in normal mode. */
87     XColor *activeFg;		/* Foreground color in active mode.  NULL
88 				 * means use normalFg instead. */
89     XColor *disabledFg;		/* Foreground color when disabled.  NULL
90 				 * means use normalFg with a 50% stipple
91 				 * instead. */
92     GC normalTextGC;		/* GC for drawing text in normal mode. */
93     GC activeTextGC;		/* GC for drawing text in active mode (NULL
94 				 * means use normalTextGC). */
95     Pixmap gray;		/* Pixmap for displaying disabled text/icon if
96 				 * disabledFg is NULL. */
97     GC disabledGC;		/* Used to produce disabled effect.  If
98 				 * disabledFg isn't NULL, this GC is used to
99 				 * draw button text or icon.  Otherwise
100 				 * text or icon is drawn with normalGC and
101 				 * this GC is used to stipple background
102 				 * across it. */
103     int leftBearing;		/* Distance from text origin to leftmost drawn
104 				 * pixel (positive means to right). */
105     int rightBearing;		/* Amount text sticks right from its origin. */
106     char *widthString;		/* Value of -width option.  Malloc'ed. */
107     char *heightString;		/* Value of -height option.  Malloc'ed. */
108     int width, height;		/* If > 0, these specify dimensions to request
109 				 * for window, in characters for text and in
110 				 * pixels for bitmaps.  In this case the actual
111 				 * size of the text string or bitmap is
112 				 * ignored in computing desired window size. */
113     int wrapLength;		/* Line length (in pixels) at which to wrap
114 				 * onto next line.  <= 0 means don't wrap
115 				 * except at newlines. */
116     int padX, padY;		/* Extra space around text or bitmap (pixels
117 				 * on each side). */
118     Tk_Anchor anchor;		/* Where text/bitmap should be displayed
119 				 * inside window region. */
120     Tk_Justify justify;		/* Justification to use for multi-line text. */
121     int textWidth;		/* Width needed to display text as requested,
122 				 * in pixels. */
123     int textHeight;		/* Height needed to display text as requested,
124 				 * in pixels. */
125     int indicatorOn;		/* Non-zero means display indicator;  0 means
126 				 * don't display. */
127     int indicatorHeight;	/* Height of indicator in pixels.  This same
128 				 * amount of extra space is also left on each
129 				 * side of the indicator. 0 if no indicator. */
130     int indicatorWidth;		/* Width of indicator in pixels, including
131 				 * indicatorHeight in padding on each side.
132 				 * 0 if no indicator. */
133 
134     /*
135      * Miscellaneous information:
136      */
137 
138     Tk_Cursor cursor;		/* Current cursor for window, or None. */
139     char *takeFocus;		/* Value of -takefocus option;  not used in
140 				 * the C code, but used by keyboard traversal
141 				 * scripts.  Malloc'ed, but may be NULL. */
142     int flags;			/* Various flags;  see below for
143 				 * definitions. */
144 } MenuButton;
145 
146 /*
147  * Flag bits for buttons:
148  *
149  * REDRAW_PENDING:		Non-zero means a DoWhenIdle handler
150  *				has already been queued to redraw
151  *				this window.
152  * POSTED:			Non-zero means that the menu associated
153  *				with this button has been posted (typically
154  *				because of an active button press).
155  * GOT_FOCUS:			Non-zero means this button currently
156  *				has the input focus.
157  */
158 
159 #define REDRAW_PENDING		1
160 #define POSTED			2
161 #define GOT_FOCUS		4
162 
163 /*
164  * The following constants define the dimensions of the cascade indicator,
165  * which is displayed if the "-indicatoron" option is true.  The units for
166  * these options are 1/10 millimeters.
167  */
168 
169 #define INDICATOR_WIDTH		40
170 #define INDICATOR_HEIGHT	17
171 
172 /*
173  * Information used for parsing configuration specs:
174  */
175 
176 static Tk_ConfigSpec configSpecs[] = {
177     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
178 	DEF_MENUBUTTON_ACTIVE_BG_COLOR, Tk_Offset(MenuButton, activeBorder),
179 	TK_CONFIG_COLOR_ONLY},
180     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
181 	DEF_MENUBUTTON_ACTIVE_BG_MONO, Tk_Offset(MenuButton, activeBorder),
182 	TK_CONFIG_MONO_ONLY},
183     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
184 	DEF_MENUBUTTON_ACTIVE_FG_COLOR, Tk_Offset(MenuButton, activeFg),
185 	TK_CONFIG_COLOR_ONLY},
186     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
187 	DEF_MENUBUTTON_ACTIVE_FG_MONO, Tk_Offset(MenuButton, activeFg),
188 	TK_CONFIG_MONO_ONLY},
189     {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor",
190 	DEF_MENUBUTTON_ANCHOR, Tk_Offset(MenuButton, anchor), 0},
191     {TK_CONFIG_BORDER, "-background", "background", "Background",
192 	DEF_MENUBUTTON_BG_COLOR, Tk_Offset(MenuButton, normalBorder),
193 	TK_CONFIG_COLOR_ONLY},
194     {TK_CONFIG_BORDER, "-background", "background", "Background",
195 	DEF_MENUBUTTON_BG_MONO, Tk_Offset(MenuButton, normalBorder),
196 	TK_CONFIG_MONO_ONLY},
197     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
198 	(char *) NULL, 0, 0},
199     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
200 	(char *) NULL, 0, 0},
201     {TK_CONFIG_BITMAP, "-bitmap", "bitmap", "Bitmap",
202 	DEF_MENUBUTTON_BITMAP, Tk_Offset(MenuButton, bitmap),
203 	TK_CONFIG_NULL_OK},
204     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
205 	DEF_MENUBUTTON_BORDER_WIDTH, Tk_Offset(MenuButton, borderWidth), 0},
206     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
207 	DEF_MENUBUTTON_CURSOR, Tk_Offset(MenuButton, cursor),
208 	TK_CONFIG_NULL_OK},
209     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
210 	"DisabledForeground", DEF_MENUBUTTON_DISABLED_FG_COLOR,
211 	Tk_Offset(MenuButton, disabledFg),
212 	TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
213     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
214 	"DisabledForeground", DEF_MENUBUTTON_DISABLED_FG_MONO,
215 	Tk_Offset(MenuButton, disabledFg),
216 	TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
217     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
218 	(char *) NULL, 0, 0},
219     {TK_CONFIG_FONT, "-font", "font", "Font",
220 	DEF_MENUBUTTON_FONT, Tk_Offset(MenuButton, fontPtr), 0},
221     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
222 	DEF_MENUBUTTON_FG, Tk_Offset(MenuButton, normalFg), 0},
223     {TK_CONFIG_STRING, "-height", "height", "Height",
224 	DEF_MENUBUTTON_HEIGHT, Tk_Offset(MenuButton, heightString), 0},
225     {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
226 	"HighlightBackground", DEF_MENUBUTTON_HIGHLIGHT_BG,
227 	Tk_Offset(MenuButton, highlightBgColorPtr), 0},
228     {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
229 	DEF_MENUBUTTON_HIGHLIGHT, Tk_Offset(MenuButton, highlightColorPtr), 0},
230     {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
231 	"HighlightThickness", DEF_MENUBUTTON_HIGHLIGHT_WIDTH,
232 	Tk_Offset(MenuButton, highlightWidth), 0},
233     {TK_CONFIG_STRING, "-image", "image", "Image",
234 	DEF_MENUBUTTON_IMAGE, Tk_Offset(MenuButton, imageString),
235 	TK_CONFIG_NULL_OK},
236     {TK_CONFIG_BOOLEAN, "-indicatoron", "indicatorOn", "IndicatorOn",
237 	DEF_MENUBUTTON_INDICATOR, Tk_Offset(MenuButton, indicatorOn), 0},
238     {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify",
239 	DEF_MENUBUTTON_JUSTIFY, Tk_Offset(MenuButton, justify), 0},
240     {TK_CONFIG_STRING, "-menu", "menu", "Menu",
241 	DEF_MENUBUTTON_MENU, Tk_Offset(MenuButton, menuName),
242 	TK_CONFIG_NULL_OK},
243     {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
244 	DEF_MENUBUTTON_PADX, Tk_Offset(MenuButton, padX), 0},
245     {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
246 	DEF_MENUBUTTON_PADY, Tk_Offset(MenuButton, padY), 0},
247     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
248 	DEF_MENUBUTTON_RELIEF, Tk_Offset(MenuButton, relief), 0},
249     {TK_CONFIG_UID, "-state", "state", "State",
250 	DEF_MENUBUTTON_STATE, Tk_Offset(MenuButton, state), 0},
251     {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
252 	DEF_MENUBUTTON_TAKE_FOCUS, Tk_Offset(MenuButton, takeFocus),
253 	TK_CONFIG_NULL_OK},
254     {TK_CONFIG_STRING, "-text", "text", "Text",
255 	DEF_MENUBUTTON_TEXT, Tk_Offset(MenuButton, text), 0},
256     {TK_CONFIG_STRING, "-textvariable", "textVariable", "Variable",
257 	DEF_MENUBUTTON_TEXT_VARIABLE, Tk_Offset(MenuButton, textVarName),
258 	TK_CONFIG_NULL_OK},
259     {TK_CONFIG_INT, "-underline", "underline", "Underline",
260 	DEF_MENUBUTTON_UNDERLINE, Tk_Offset(MenuButton, underline), 0},
261     {TK_CONFIG_STRING, "-width", "width", "Width",
262 	DEF_MENUBUTTON_WIDTH, Tk_Offset(MenuButton, widthString), 0},
263     {TK_CONFIG_PIXELS, "-wraplength", "wrapLength", "WrapLength",
264 	DEF_MENUBUTTON_WRAP_LENGTH, Tk_Offset(MenuButton, wrapLength), 0},
265     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
266 	(char *) NULL, 0, 0}
267 };
268 
269 /*
270  * Forward declarations for procedures defined later in this file:
271  */
272 
273 static void		ComputeMenuButtonGeometry _ANSI_ARGS_((
274 			    MenuButton *mbPtr));
275 static void		MenuButtonCmdDeletedProc _ANSI_ARGS_((
276 			    ClientData clientData));
277 static void		MenuButtonEventProc _ANSI_ARGS_((ClientData clientData,
278 			    XEvent *eventPtr));
279 static void		MenuButtonImageProc _ANSI_ARGS_((ClientData clientData,
280 			    int x, int y, int width, int height, int imgWidth,
281 			    int imgHeight));
282 static char *		MenuButtonTextVarProc _ANSI_ARGS_((
283 			    ClientData clientData, Tcl_Interp *interp,
284 			    char *name1, char *name2, int flags));
285 static int		MenuButtonWidgetCmd _ANSI_ARGS_((ClientData clientData,
286 			    Tcl_Interp *interp, int argc, char **argv));
287 static int		ConfigureMenuButton _ANSI_ARGS_((Tcl_Interp *interp,
288 			    MenuButton *mbPtr, int argc, char **argv,
289 			    int flags));
290 static void		DestroyMenuButton _ANSI_ARGS_((char *memPtr));
291 static void		DisplayMenuButton _ANSI_ARGS_((ClientData clientData));
292 
293 /*
294  *--------------------------------------------------------------
295  *
296  * Tk_MenubuttonCmd --
297  *
298  *	This procedure is invoked to process the "button", "label",
299  *	"radiobutton", and "checkbutton" Tcl commands.  See the
300  *	user documentation for details on what it does.
301  *
302  * Results:
303  *	A standard Tcl result.
304  *
305  * Side effects:
306  *	See the user documentation.
307  *
308  *--------------------------------------------------------------
309  */
310 
311 int
Tk_MenubuttonCmd(clientData,interp,argc,argv)312 Tk_MenubuttonCmd(clientData, interp, argc, argv)
313     ClientData clientData;	/* Main window associated with
314 				 * interpreter. */
315     Tcl_Interp *interp;		/* Current interpreter. */
316     int argc;			/* Number of arguments. */
317     char **argv;		/* Argument strings. */
318 {
319     register MenuButton *mbPtr;
320     Tk_Window tkwin = (Tk_Window) clientData;
321     Tk_Window new;
322 
323     if (argc < 2) {
324 	Tcl_AppendResult(interp, "wrong # args: should be \"",
325 		argv[0], " pathName ?options?\"", (char *) NULL);
326 	return TCL_ERROR;
327     }
328 
329     /*
330      * Create the new window.
331      */
332 
333     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
334     if (new == NULL) {
335 	return TCL_ERROR;
336     }
337 
338     /*
339      * Initialize the data structure for the button.
340      */
341 
342     mbPtr = (MenuButton *) ckalloc(sizeof(MenuButton));
343     mbPtr->tkwin = new;
344     mbPtr->display = Tk_Display (new);
345     mbPtr->interp = interp;
346     mbPtr->widgetCmd = Tcl_CreateCommand(interp, Tk_PathName(mbPtr->tkwin),
347 	    MenuButtonWidgetCmd, (ClientData) mbPtr, MenuButtonCmdDeletedProc);
348     mbPtr->menuName = NULL;
349     mbPtr->text = NULL;
350     mbPtr->numChars = 0;
351     mbPtr->underline = -1;
352     mbPtr->textVarName = NULL;
353     mbPtr->bitmap = None;
354     mbPtr->imageString = NULL;
355     mbPtr->image = NULL;
356     mbPtr->state = tkNormalUid;
357     mbPtr->normalBorder = NULL;
358     mbPtr->activeBorder = NULL;
359     mbPtr->borderWidth = 0;
360     mbPtr->relief = TK_RELIEF_FLAT;
361     mbPtr->highlightWidth = 0;
362     mbPtr->highlightBgColorPtr = NULL;
363     mbPtr->highlightColorPtr = NULL;
364     mbPtr->inset = 0;
365     mbPtr->fontPtr = NULL;
366     mbPtr->normalFg = NULL;
367     mbPtr->activeFg = NULL;
368     mbPtr->disabledFg = NULL;
369     mbPtr->normalTextGC = None;
370     mbPtr->activeTextGC = None;
371     mbPtr->gray = None;
372     mbPtr->disabledGC = None;
373     mbPtr->leftBearing = 0;
374     mbPtr->rightBearing = 0;
375     mbPtr->widthString = NULL;
376     mbPtr->heightString = NULL;
377     mbPtr->width = 0;
378     mbPtr->width = 0;
379     mbPtr->wrapLength = 0;
380     mbPtr->padX = 0;
381     mbPtr->padY = 0;
382     mbPtr->anchor = TK_ANCHOR_CENTER;
383     mbPtr->justify = TK_JUSTIFY_CENTER;
384     mbPtr->indicatorOn = 0;
385     mbPtr->indicatorWidth = 0;
386     mbPtr->indicatorHeight = 0;
387     mbPtr->cursor = None;
388     mbPtr->takeFocus = NULL;
389     mbPtr->flags = 0;
390 
391     Tk_SetClass(mbPtr->tkwin, "Menubutton");
392     Tk_CreateEventHandler(mbPtr->tkwin,
393 	    ExposureMask|StructureNotifyMask|FocusChangeMask,
394 	    MenuButtonEventProc, (ClientData) mbPtr);
395     if (ConfigureMenuButton(interp, mbPtr, argc-2, argv+2, 0) != TCL_OK) {
396 	Tk_DestroyWindow(mbPtr->tkwin);
397 	return TCL_ERROR;
398     }
399 
400     interp->result = Tk_PathName(mbPtr->tkwin);
401     return TCL_OK;
402 }
403 
404 /*
405  *--------------------------------------------------------------
406  *
407  * MenuButtonWidgetCmd --
408  *
409  *	This procedure is invoked to process the Tcl command
410  *	that corresponds to a widget managed by this module.
411  *	See the user documentation for details on what it does.
412  *
413  * Results:
414  *	A standard Tcl result.
415  *
416  * Side effects:
417  *	See the user documentation.
418  *
419  *--------------------------------------------------------------
420  */
421 
422 static int
MenuButtonWidgetCmd(clientData,interp,argc,argv)423 MenuButtonWidgetCmd(clientData, interp, argc, argv)
424     ClientData clientData;	/* Information about button widget. */
425     Tcl_Interp *interp;		/* Current interpreter. */
426     int argc;			/* Number of arguments. */
427     char **argv;		/* Argument strings. */
428 {
429     register MenuButton *mbPtr = (MenuButton *) clientData;
430     int result = TCL_OK;
431     size_t length;
432     int c;
433 
434     if (argc < 2) {
435 	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
436 		" option ?arg arg ...?\"", (char *) NULL);
437 	return TCL_ERROR;
438     }
439     Tcl_Preserve((ClientData) mbPtr);
440     c = argv[1][0];
441     length = strlen(argv[1]);
442     if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
443 	    && (length >= 2)) {
444 	if (argc != 3) {
445 	    Tcl_AppendResult(interp, "wrong # args: should be \"",
446 		    argv[0], " cget option\"",
447 		    (char *) NULL);
448 	    goto error;
449 	}
450 	result = Tk_ConfigureValue(interp, mbPtr->tkwin, configSpecs,
451 		(char *) mbPtr, argv[2], 0);
452     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
453 	    && (length >= 2)) {
454 	if (argc == 2) {
455 	    result = Tk_ConfigureInfo(interp, mbPtr->tkwin, configSpecs,
456 		    (char *) mbPtr, (char *) NULL, 0);
457 	} else if (argc == 3) {
458 	    result = Tk_ConfigureInfo(interp, mbPtr->tkwin, configSpecs,
459 		    (char *) mbPtr, argv[2], 0);
460 	} else {
461 	    result = ConfigureMenuButton(interp, mbPtr, argc-2, argv+2,
462 		    TK_CONFIG_ARGV_ONLY);
463 	}
464     } else {
465 	Tcl_AppendResult(interp, "bad option \"", argv[1],
466 		"\": must be cget or configure",
467 		(char *) NULL);
468 	goto error;
469     }
470     Tcl_Release((ClientData) mbPtr);
471     return result;
472 
473     error:
474     Tcl_Release((ClientData) mbPtr);
475     return TCL_ERROR;
476 }
477 
478 /*
479  *----------------------------------------------------------------------
480  *
481  * DestroyMenuButton --
482  *
483  *	This procedure is invoked to recycle all of the resources
484  *	associated with a button widget.  It is invoked as a
485  *	when-idle handler in order to make sure that there is no
486  *	other use of the button pending at the time of the deletion.
487  *
488  * Results:
489  *	None.
490  *
491  * Side effects:
492  *	Everything associated with the widget is freed up.
493  *
494  *----------------------------------------------------------------------
495  */
496 
497 static void
DestroyMenuButton(memPtr)498 DestroyMenuButton(memPtr)
499     char *memPtr;		/* Info about button widget. */
500 {
501     register MenuButton *mbPtr = (MenuButton *) memPtr;
502 
503     /*
504      * Free up all the stuff that requires special handling, then
505      * let Tk_FreeOptions handle all the standard option-related
506      * stuff.
507      */
508 
509     if (mbPtr->textVarName != NULL) {
510 	Tcl_UntraceVar(mbPtr->interp, mbPtr->textVarName,
511 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
512 		MenuButtonTextVarProc, (ClientData) mbPtr);
513     }
514     if (mbPtr->image != NULL) {
515 	Tk_FreeImage(mbPtr->image);
516     }
517     if (mbPtr->normalTextGC != None) {
518 	Tk_FreeGC(mbPtr->display, mbPtr->normalTextGC);
519     }
520     if (mbPtr->activeTextGC != None) {
521 	Tk_FreeGC(mbPtr->display, mbPtr->activeTextGC);
522     }
523     if (mbPtr->gray != None) {
524 	Tk_FreeBitmap(mbPtr->display, mbPtr->gray);
525     }
526     if (mbPtr->disabledGC != None) {
527 	Tk_FreeGC(mbPtr->display, mbPtr->disabledGC);
528     }
529     Tk_FreeOptions(configSpecs, (char *) mbPtr, mbPtr->display, 0);
530     ckfree((char *) mbPtr);
531 }
532 
533 /*
534  *----------------------------------------------------------------------
535  *
536  * ConfigureMenuButton --
537  *
538  *	This procedure is called to process an argv/argc list, plus
539  *	the Tk option database, in order to configure (or
540  *	reconfigure) a menubutton widget.
541  *
542  * Results:
543  *	The return value is a standard Tcl result.  If TCL_ERROR is
544  *	returned, then interp->result contains an error message.
545  *
546  * Side effects:
547  *	Configuration information, such as text string, colors, font,
548  *	etc. get set for mbPtr;  old resources get freed, if there
549  *	were any.  The menubutton is redisplayed.
550  *
551  *----------------------------------------------------------------------
552  */
553 
554 static int
ConfigureMenuButton(interp,mbPtr,argc,argv,flags)555 ConfigureMenuButton(interp, mbPtr, argc, argv, flags)
556     Tcl_Interp *interp;		/* Used for error reporting. */
557     register MenuButton *mbPtr;	/* Information about widget;  may or may
558 				 * not already have values for some fields. */
559     int argc;			/* Number of valid entries in argv. */
560     char **argv;		/* Arguments. */
561     int flags;			/* Flags to pass to Tk_ConfigureWidget. */
562 {
563     XGCValues gcValues;
564     GC newGC;
565     unsigned long mask;
566     int result;
567     Tk_Image image;
568 
569     /*
570      * Eliminate any existing trace on variables monitored by the menubutton.
571      */
572 
573     if (mbPtr->textVarName != NULL) {
574 	Tcl_UntraceVar(interp, mbPtr->textVarName,
575 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
576 		MenuButtonTextVarProc, (ClientData) mbPtr);
577     }
578 
579     result = Tk_ConfigureWidget(interp, mbPtr->tkwin, configSpecs,
580 	    argc, argv, (char *) mbPtr, flags);
581     if (result != TCL_OK) {
582 	return TCL_ERROR;
583     }
584 
585     /*
586      * A few options need special processing, such as setting the
587      * background from a 3-D border, or filling in complicated
588      * defaults that couldn't be specified to Tk_ConfigureWidget.
589      */
590 
591     if ((mbPtr->state == tkActiveUid) && !Tk_StrictMotif(mbPtr->tkwin)) {
592 	Tk_SetBackgroundFromBorder(mbPtr->tkwin, mbPtr->activeBorder);
593     } else {
594 	Tk_SetBackgroundFromBorder(mbPtr->tkwin, mbPtr->normalBorder);
595 	if ((mbPtr->state != tkNormalUid) && (mbPtr->state != tkActiveUid)
596 		&& (mbPtr->state != tkDisabledUid)) {
597 	    Tcl_AppendResult(interp, "bad state value \"", mbPtr->state,
598 		    "\": must be normal, active, or disabled", (char *) NULL);
599 	    mbPtr->state = tkNormalUid;
600 	    return TCL_ERROR;
601 	}
602     }
603 
604     if (mbPtr->highlightWidth < 0) {
605 	mbPtr->highlightWidth = 0;
606     }
607 
608     gcValues.font = mbPtr->fontPtr->fid;
609     gcValues.foreground = mbPtr->normalFg->pixel;
610     gcValues.background = Tk_3DBorderColor(mbPtr->normalBorder)->pixel;
611 
612     /*
613      * Note: GraphicsExpose events are disabled in GC's because they're
614      * used to copy stuff from an off-screen pixmap onto the screen (we know
615      * that there's no problem with obscured areas).
616      */
617 
618     gcValues.graphics_exposures = False;
619     newGC = Tk_GetGC(mbPtr->tkwin,
620 	    GCForeground|GCBackground|GCFont|GCGraphicsExposures, &gcValues);
621     if (mbPtr->normalTextGC != None) {
622 	Tk_FreeGC(mbPtr->display, mbPtr->normalTextGC);
623     }
624     mbPtr->normalTextGC = newGC;
625 
626     gcValues.font = mbPtr->fontPtr->fid;
627     gcValues.foreground = mbPtr->activeFg->pixel;
628     gcValues.background = Tk_3DBorderColor(mbPtr->activeBorder)->pixel;
629     newGC = Tk_GetGC(mbPtr->tkwin, GCForeground|GCBackground|GCFont,
630 	    &gcValues);
631     if (mbPtr->activeTextGC != None) {
632 	Tk_FreeGC(mbPtr->display, mbPtr->activeTextGC);
633     }
634     mbPtr->activeTextGC = newGC;
635 
636     gcValues.font = mbPtr->fontPtr->fid;
637     gcValues.background = Tk_3DBorderColor(mbPtr->normalBorder)->pixel;
638     if ((mbPtr->disabledFg != NULL) && (mbPtr->imageString == NULL)) {
639 	gcValues.foreground = mbPtr->disabledFg->pixel;
640 	mask = GCForeground|GCBackground|GCFont;
641     } else {
642 	gcValues.foreground = gcValues.background;
643 	if (mbPtr->gray == None) {
644 	    mbPtr->gray = Tk_GetBitmap(interp, mbPtr->tkwin,
645 		    Tk_GetUid("gray50"));
646 	    if (mbPtr->gray == None) {
647 		return TCL_ERROR;
648 	    }
649 	}
650 	gcValues.fill_style = FillStippled;
651 	gcValues.stipple = mbPtr->gray;
652 	mask = GCForeground|GCFillStyle|GCStipple;
653     }
654     newGC = Tk_GetGC(mbPtr->tkwin, mask, &gcValues);
655     if (mbPtr->disabledGC != None) {
656 	Tk_FreeGC(mbPtr->display, mbPtr->disabledGC);
657     }
658     mbPtr->disabledGC = newGC;
659 
660     if (mbPtr->padX < 0) {
661 	mbPtr->padX = 0;
662     }
663     if (mbPtr->padY < 0) {
664 	mbPtr->padY = 0;
665     }
666 
667     /*
668      * Get the image for the widget, if there is one.  Allocate the
669      * new image before freeing the old one, so that the reference
670      * count doesn't go to zero and cause image data to be discarded.
671      */
672 
673     if (mbPtr->imageString != NULL) {
674 	image = Tk_GetImage(mbPtr->interp, mbPtr->tkwin,
675 		mbPtr->imageString, MenuButtonImageProc, (ClientData) mbPtr);
676 	if (image == NULL) {
677 	    return TCL_ERROR;
678 	}
679     } else {
680 	image = NULL;
681     }
682     if (mbPtr->image != NULL) {
683 	Tk_FreeImage(mbPtr->image);
684     }
685     mbPtr->image = image;
686 
687     if ((mbPtr->image == NULL) && (mbPtr->bitmap == None)
688 	    && (mbPtr->textVarName != NULL)) {
689 	/*
690 	 * The menubutton displays a variable.  Set up a trace to watch
691 	 * for any changes in it.
692 	 */
693 
694 	char *value;
695 
696 	value = Tcl_GetVar(interp, mbPtr->textVarName, TCL_GLOBAL_ONLY);
697 	if (value == NULL) {
698 	    Tcl_SetVar(interp, mbPtr->textVarName, mbPtr->text,
699 		    TCL_GLOBAL_ONLY);
700 	} else {
701 	    if (mbPtr->text != NULL) {
702 		ckfree(mbPtr->text);
703 	    }
704 	    mbPtr->text = (char *) ckalloc((unsigned) (strlen(value) + 1));
705 	    strcpy(mbPtr->text, value);
706 	}
707 	Tcl_TraceVar(interp, mbPtr->textVarName,
708 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
709 		MenuButtonTextVarProc, (ClientData) mbPtr);
710     }
711 
712     /*
713      * Recompute the geometry for the button.
714      */
715 
716     if ((mbPtr->bitmap != None) || (mbPtr->image != NULL)) {
717 	if (Tk_GetPixels(interp, mbPtr->tkwin, mbPtr->widthString,
718 		&mbPtr->width) != TCL_OK) {
719 	    widthError:
720 	    Tcl_AddErrorInfo(interp, "\n    (processing -width option)");
721 	    return TCL_ERROR;
722 	}
723 	if (Tk_GetPixels(interp, mbPtr->tkwin, mbPtr->heightString,
724 		&mbPtr->height) != TCL_OK) {
725 	    heightError:
726 	    Tcl_AddErrorInfo(interp, "\n    (processing -height option)");
727 	    return TCL_ERROR;
728 	}
729     } else {
730 	if (Tcl_GetInt(interp, mbPtr->widthString, &mbPtr->width)
731 		!= TCL_OK) {
732 	    goto widthError;
733 	}
734 	if (Tcl_GetInt(interp, mbPtr->heightString, &mbPtr->height)
735 		!= TCL_OK) {
736 	    goto heightError;
737 	}
738     }
739     ComputeMenuButtonGeometry(mbPtr);
740 
741     /*
742      * Lastly, arrange for the button to be redisplayed.
743      */
744 
745     if (Tk_IsMapped(mbPtr->tkwin) && !(mbPtr->flags & REDRAW_PENDING)) {
746 	Tcl_DoWhenIdle(DisplayMenuButton, (ClientData) mbPtr);
747 	mbPtr->flags |= REDRAW_PENDING;
748     }
749 
750     return TCL_OK;
751 }
752 
753 /*
754  *----------------------------------------------------------------------
755  *
756  * DisplayMenuButton --
757  *
758  *	This procedure is invoked to display a menubutton widget.
759  *
760  * Results:
761  *	None.
762  *
763  * Side effects:
764  *	Commands are output to X to display the menubutton in its
765  *	current mode.
766  *
767  *----------------------------------------------------------------------
768  */
769 
770 static void
DisplayMenuButton(clientData)771 DisplayMenuButton(clientData)
772     ClientData clientData;	/* Information about widget. */
773 {
774     register MenuButton *mbPtr = (MenuButton *) clientData;
775     GC gc;
776     Tk_3DBorder border;
777     Pixmap pixmap;
778     int x = 0;			/* Initialization needed only to stop
779 				 * compiler warning. */
780     int y;
781     register Tk_Window tkwin = mbPtr->tkwin;
782     int width, height;
783 
784     mbPtr->flags &= ~REDRAW_PENDING;
785     if ((mbPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
786 	return;
787     }
788 
789     if ((mbPtr->state == tkDisabledUid) && (mbPtr->disabledFg != NULL)) {
790 	gc = mbPtr->disabledGC;
791 	border = mbPtr->normalBorder;
792     } else if ((mbPtr->state == tkActiveUid) && !Tk_StrictMotif(mbPtr->tkwin)) {
793 	gc = mbPtr->activeTextGC;
794 	border = mbPtr->activeBorder;
795     } else {
796 	gc = mbPtr->normalTextGC;
797 	border = mbPtr->normalBorder;
798     }
799 
800     /*
801      * In order to avoid screen flashes, this procedure redraws
802      * the menu button in a pixmap, then copies the pixmap to the
803      * screen in a single operation.  This means that there's no
804      * point in time where the on-sreen image has been cleared.
805      */
806 
807     pixmap = Tk_GetPixmap(mbPtr->display, Tk_WindowId(tkwin),
808 	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
809     Tk_Fill3DRectangle(tkwin, pixmap, border, 0, 0, Tk_Width(tkwin),
810 	    Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
811 
812     /*
813      * Display image or bitmap or text for button.
814      */
815 
816     if (mbPtr->image != None) {
817 	Tk_SizeOfImage(mbPtr->image, &width, &height);
818 
819 	imageOrBitmap:
820 	switch (mbPtr->anchor) {
821 	    case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW:
822 		x += mbPtr->inset;
823 		break;
824 	    case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S:
825 		x += ((int) (Tk_Width(tkwin) - width
826 			- mbPtr->indicatorWidth))/2;
827 		break;
828 	    default:
829 		x += Tk_Width(tkwin) - mbPtr->inset - width
830 			- mbPtr->indicatorWidth;
831 		break;
832 	}
833 	switch (mbPtr->anchor) {
834 	    case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE:
835 		y = mbPtr->inset;
836 		break;
837 	    case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E:
838 		y = ((int) (Tk_Height(tkwin) - height))/2;
839 		break;
840 	    default:
841 		y = Tk_Height(tkwin) - mbPtr->inset - height;
842 		break;
843 	}
844 	if (mbPtr->image != NULL) {
845 	    Tk_RedrawImage(mbPtr->image, 0, 0, width, height, pixmap,
846 		    x, y);
847 	} else {
848 	    XCopyPlane(mbPtr->display, mbPtr->bitmap, pixmap,
849 		    gc, 0, 0, (unsigned) width, (unsigned) height, x, y, 1);
850 	}
851     } else if (mbPtr->bitmap != None) {
852 	Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height);
853 	goto imageOrBitmap;
854     } else {
855 	width = mbPtr->textWidth;
856 	height = mbPtr->textHeight;
857 	switch (mbPtr->anchor) {
858 	    case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW:
859 		x = mbPtr->inset + mbPtr->padX;
860 		break;
861 	    case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S:
862 		x = ((int) (Tk_Width(tkwin) - width
863 			- mbPtr->indicatorWidth))/2;
864 		break;
865 	    default:
866 		x = Tk_Width(tkwin) - width - mbPtr->padX - mbPtr->inset
867 			- mbPtr->indicatorWidth;
868 		break;
869 	}
870 	switch (mbPtr->anchor) {
871 	    case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE:
872 		y = mbPtr->inset + mbPtr->padY;
873 		break;
874 	    case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E:
875 		y = ((int) (Tk_Height(tkwin) - height))/2;
876 		break;
877 	    default:
878 		y = Tk_Height(tkwin) - mbPtr->inset - mbPtr->padY - height;
879 		break;
880 	}
881 	TkDisplayText(mbPtr->display, pixmap, mbPtr->fontPtr,
882 		mbPtr->text, mbPtr->numChars, x, y, mbPtr->textWidth,
883 		mbPtr->justify, mbPtr->underline, gc);
884     }
885 
886     /*
887      * If the menu button is disabled with a stipple rather than a special
888      * foreground color, generate the stippled effect.
889      */
890 
891     if ((mbPtr->state == tkDisabledUid)
892 	    && ((mbPtr->disabledFg == NULL) || (mbPtr->image != NULL))) {
893 	XFillRectangle(mbPtr->display, pixmap, mbPtr->disabledGC,
894 		mbPtr->inset, mbPtr->inset,
895 		(unsigned) (Tk_Width(tkwin) - 2*mbPtr->inset),
896 		(unsigned) (Tk_Height(tkwin) - 2*mbPtr->inset));
897     }
898 
899     /*
900      * Draw the cascade indicator for the menu button on the
901      * right side of the window, if desired.
902      */
903 
904     if (mbPtr->indicatorOn) {
905 	int borderWidth;
906 
907 	borderWidth = (mbPtr->indicatorHeight+1)/3;
908 	if (borderWidth < 1) {
909 	    borderWidth = 1;
910 	}
911 	Tk_Fill3DRectangle(tkwin, pixmap, border,
912 		Tk_Width(tkwin) - mbPtr->inset - mbPtr->indicatorWidth
913 		    + mbPtr->indicatorHeight,
914 		y + ((int) (height - mbPtr->indicatorHeight))/2,
915 		mbPtr->indicatorWidth - 2*mbPtr->indicatorHeight,
916 		mbPtr->indicatorHeight, borderWidth, TK_RELIEF_RAISED);
917     }
918 
919     /*
920      * Draw the border and traversal highlight last.  This way, if the
921      * menu button's contents overflow onto the border they'll be covered
922      * up by the border.
923      */
924 
925     if (mbPtr->relief != TK_RELIEF_FLAT) {
926 	Tk_Draw3DRectangle(tkwin, pixmap, border,
927 		mbPtr->highlightWidth, mbPtr->highlightWidth,
928 		Tk_Width(tkwin) - 2*mbPtr->highlightWidth,
929 		Tk_Height(tkwin) - 2*mbPtr->highlightWidth,
930 		mbPtr->borderWidth, mbPtr->relief);
931     }
932     if (mbPtr->highlightWidth != 0) {
933 	GC gc;
934 
935 	if (mbPtr->flags & GOT_FOCUS) {
936 	    gc = Tk_GCForColor(mbPtr->highlightColorPtr, pixmap);
937 	} else {
938 	    gc = Tk_GCForColor(mbPtr->highlightBgColorPtr, pixmap);
939 	}
940 	Tk_DrawFocusHighlight(tkwin, gc, mbPtr->highlightWidth, pixmap);
941     }
942 
943     /*
944      * Copy the information from the off-screen pixmap onto the screen,
945      * then delete the pixmap.
946      */
947 
948     XCopyArea(mbPtr->display, pixmap, Tk_WindowId(tkwin),
949 	    mbPtr->normalTextGC, 0, 0, (unsigned) Tk_Width(tkwin),
950 	    (unsigned) Tk_Height(tkwin), 0, 0);
951     Tk_FreePixmap(mbPtr->display, pixmap);
952 }
953 
954 /*
955  *--------------------------------------------------------------
956  *
957  * MenuButtonEventProc --
958  *
959  *	This procedure is invoked by the Tk dispatcher for various
960  *	events on buttons.
961  *
962  * Results:
963  *	None.
964  *
965  * Side effects:
966  *	When the window gets deleted, internal structures get
967  *	cleaned up.  When it gets exposed, it is redisplayed.
968  *
969  *--------------------------------------------------------------
970  */
971 
972 static void
MenuButtonEventProc(clientData,eventPtr)973 MenuButtonEventProc(clientData, eventPtr)
974     ClientData clientData;	/* Information about window. */
975     XEvent *eventPtr;		/* Information about event. */
976 {
977     MenuButton *mbPtr = (MenuButton *) clientData;
978     if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
979 	goto redraw;
980     } else if (eventPtr->type == ConfigureNotify) {
981 	/*
982 	 * Must redraw after size changes, since layout could have changed
983 	 * and borders will need to be redrawn.
984 	 */
985 
986 	goto redraw;
987     } else if (eventPtr->type == DestroyNotify) {
988 	if (mbPtr->tkwin != NULL) {
989 	    mbPtr->tkwin = NULL;
990 	    Tcl_DeleteCommand(mbPtr->interp,
991 		    Tcl_GetCommandName(mbPtr->interp, mbPtr->widgetCmd));
992 	}
993 	if (mbPtr->flags & REDRAW_PENDING) {
994 	    Tcl_CancelIdleCall(DisplayMenuButton, (ClientData) mbPtr);
995 	}
996 	Tcl_EventuallyFree((ClientData) mbPtr, DestroyMenuButton);
997     } else if (eventPtr->type == FocusIn) {
998 	if (eventPtr->xfocus.detail != NotifyInferior) {
999 	    mbPtr->flags |= GOT_FOCUS;
1000 	    if (mbPtr->highlightWidth > 0) {
1001 		goto redraw;
1002 	    }
1003 	}
1004     } else if (eventPtr->type == FocusOut) {
1005 	if (eventPtr->xfocus.detail != NotifyInferior) {
1006 	    mbPtr->flags &= ~GOT_FOCUS;
1007 	    if (mbPtr->highlightWidth > 0) {
1008 		goto redraw;
1009 	    }
1010 	}
1011     }
1012     return;
1013 
1014     redraw:
1015     if ((mbPtr->tkwin != NULL) && !(mbPtr->flags & REDRAW_PENDING)) {
1016 	Tcl_DoWhenIdle(DisplayMenuButton, (ClientData) mbPtr);
1017 	mbPtr->flags |= REDRAW_PENDING;
1018     }
1019 }
1020 
1021 /*
1022  *----------------------------------------------------------------------
1023  *
1024  * MenuButtonCmdDeletedProc --
1025  *
1026  *	This procedure is invoked when a widget command is deleted.  If
1027  *	the widget isn't already in the process of being destroyed,
1028  *	this command destroys it.
1029  *
1030  * Results:
1031  *	None.
1032  *
1033  * Side effects:
1034  *	The widget is destroyed.
1035  *
1036  *----------------------------------------------------------------------
1037  */
1038 
1039 static void
MenuButtonCmdDeletedProc(clientData)1040 MenuButtonCmdDeletedProc(clientData)
1041     ClientData clientData;	/* Pointer to widget record for widget. */
1042 {
1043     MenuButton *mbPtr = (MenuButton *) clientData;
1044     Tk_Window tkwin = mbPtr->tkwin;
1045 
1046     /*
1047      * This procedure could be invoked either because the window was
1048      * destroyed and the command was then deleted (in which case tkwin
1049      * is NULL) or because the command was deleted, and then this procedure
1050      * destroys the widget.
1051      */
1052 
1053     if (tkwin != NULL) {
1054 	mbPtr->tkwin = NULL;
1055 	Tk_DestroyWindow(tkwin);
1056     }
1057 }
1058 
1059 /*
1060  *----------------------------------------------------------------------
1061  *
1062  * ComputeMenuButtonGeometry --
1063  *
1064  *	After changes in a menu button's text or bitmap, this procedure
1065  *	recomputes the menu button's geometry and passes this information
1066  *	along to the geometry manager for the window.
1067  *
1068  * Results:
1069  *	None.
1070  *
1071  * Side effects:
1072  *	The menu button's window may change size.
1073  *
1074  *----------------------------------------------------------------------
1075  */
1076 
1077 static void
ComputeMenuButtonGeometry(mbPtr)1078 ComputeMenuButtonGeometry(mbPtr)
1079     register MenuButton *mbPtr;		/* Widget record for menu button. */
1080 {
1081     int width, height, mm, pixels;
1082 
1083     mbPtr->inset = mbPtr->highlightWidth + mbPtr->borderWidth;
1084     if (mbPtr->image != None) {
1085 	Tk_SizeOfImage(mbPtr->image, &width, &height);
1086 	if (mbPtr->width > 0) {
1087 	    width = mbPtr->width;
1088 	}
1089 	if (mbPtr->height > 0) {
1090 	    height = mbPtr->height;
1091 	}
1092     } else if (mbPtr->bitmap != None) {
1093 	Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height);
1094 	if (mbPtr->width > 0) {
1095 	    width = mbPtr->width;
1096 	}
1097 	if (mbPtr->height > 0) {
1098 	    height = mbPtr->height;
1099 	}
1100     } else {
1101 	mbPtr->numChars = strlen(mbPtr->text);
1102 	TkComputeTextGeometry(mbPtr->fontPtr, mbPtr->text,
1103 		mbPtr->numChars, mbPtr->wrapLength, &mbPtr->textWidth,
1104 		&mbPtr->textHeight);
1105 	width = mbPtr->textWidth;
1106 	height = mbPtr->textHeight;
1107 	if (mbPtr->width > 0) {
1108 	    width = mbPtr->width * XTextWidth(mbPtr->fontPtr, "0", 1);
1109 	}
1110 	if (mbPtr->height > 0) {
1111 	    height = mbPtr->height * (mbPtr->fontPtr->ascent
1112 		    + mbPtr->fontPtr->descent);
1113 	}
1114 	width += 2*mbPtr->padX;
1115 	height += 2*mbPtr->padY;
1116     }
1117 
1118     if (mbPtr->indicatorOn) {
1119 	mm = WidthMMOfScreen(Tk_Screen(mbPtr->tkwin));
1120 	pixels = WidthOfScreen(Tk_Screen(mbPtr->tkwin));
1121 	mbPtr->indicatorHeight= (INDICATOR_HEIGHT * pixels)/(10*mm);
1122 	mbPtr->indicatorWidth = (INDICATOR_WIDTH * pixels)/(10*mm)
1123 		+ 2*mbPtr->indicatorHeight;
1124 	width += mbPtr->indicatorWidth;
1125     } else {
1126 	mbPtr->indicatorHeight = 0;
1127 	mbPtr->indicatorWidth = 0;
1128     }
1129 
1130     Tk_GeometryRequest(mbPtr->tkwin, (int) (width + 2*mbPtr->inset),
1131 	    (int) (height + 2*mbPtr->inset));
1132     Tk_SetInternalBorder(mbPtr->tkwin, mbPtr->inset);
1133 }
1134 
1135 /*
1136  *--------------------------------------------------------------
1137  *
1138  * MenuButtonTextVarProc --
1139  *
1140  *	This procedure is invoked when someone changes the variable
1141  *	whose contents are to be displayed in a menu button.
1142  *
1143  * Results:
1144  *	NULL is always returned.
1145  *
1146  * Side effects:
1147  *	The text displayed in the menu button will change to match the
1148  *	variable.
1149  *
1150  *--------------------------------------------------------------
1151  */
1152 
1153 	/* ARGSUSED */
1154 static char *
MenuButtonTextVarProc(clientData,interp,name1,name2,flags)1155 MenuButtonTextVarProc(clientData, interp, name1, name2, flags)
1156     ClientData clientData;	/* Information about button. */
1157     Tcl_Interp *interp;		/* Interpreter containing variable. */
1158     char *name1;		/* Name of variable. */
1159     char *name2;		/* Second part of variable name. */
1160     int flags;			/* Information about what happened. */
1161 {
1162     register MenuButton *mbPtr = (MenuButton *) clientData;
1163     char *value;
1164 
1165     /*
1166      * If the variable is unset, then immediately recreate it unless
1167      * the whole interpreter is going away.
1168      */
1169 
1170     if (flags & TCL_TRACE_UNSETS) {
1171 	if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
1172 	    Tcl_SetVar(interp, mbPtr->textVarName, mbPtr->text,
1173 		    TCL_GLOBAL_ONLY);
1174 	    Tcl_TraceVar(interp, mbPtr->textVarName,
1175 		    TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1176 		    MenuButtonTextVarProc, clientData);
1177 	}
1178 	return (char *) NULL;
1179     }
1180 
1181     value = Tcl_GetVar(interp, mbPtr->textVarName, TCL_GLOBAL_ONLY);
1182     if (value == NULL) {
1183 	value = "";
1184     }
1185     if (mbPtr->text != NULL) {
1186 	ckfree(mbPtr->text);
1187     }
1188     mbPtr->text = (char *) ckalloc((unsigned) (strlen(value) + 1));
1189     strcpy(mbPtr->text, value);
1190     ComputeMenuButtonGeometry(mbPtr);
1191 
1192     if ((mbPtr->tkwin != NULL) && Tk_IsMapped(mbPtr->tkwin)
1193 	    && !(mbPtr->flags & REDRAW_PENDING)) {
1194 	Tcl_DoWhenIdle(DisplayMenuButton, (ClientData) mbPtr);
1195 	mbPtr->flags |= REDRAW_PENDING;
1196     }
1197     return (char *) NULL;
1198 }
1199 
1200 /*
1201  *----------------------------------------------------------------------
1202  *
1203  * MenuButtonImageProc --
1204  *
1205  *	This procedure is invoked by the image code whenever the manager
1206  *	for an image does something that affects the size of contents
1207  *	of an image displayed in a button.
1208  *
1209  * Results:
1210  *	None.
1211  *
1212  * Side effects:
1213  *	Arranges for the button to get redisplayed.
1214  *
1215  *----------------------------------------------------------------------
1216  */
1217 
1218 static void
MenuButtonImageProc(clientData,x,y,width,height,imgWidth,imgHeight)1219 MenuButtonImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
1220     ClientData clientData;		/* Pointer to widget record. */
1221     int x, y;				/* Upper left pixel (within image)
1222 					 * that must be redisplayed. */
1223     int width, height;			/* Dimensions of area to redisplay
1224 					 * (may be <= 0). */
1225     int imgWidth, imgHeight;		/* New dimensions of image. */
1226 {
1227     register MenuButton *mbPtr = (MenuButton *) clientData;
1228 
1229     if (mbPtr->tkwin != NULL) {
1230 	ComputeMenuButtonGeometry(mbPtr);
1231 	if (Tk_IsMapped(mbPtr->tkwin) && !(mbPtr->flags & REDRAW_PENDING)) {
1232 	    Tcl_DoWhenIdle(DisplayMenuButton, (ClientData) mbPtr);
1233 	    mbPtr->flags |= REDRAW_PENDING;
1234 	}
1235     }
1236 }
1237