OSDN Git Service

Please enter the commit message for your changes. Lines starting
[eos/base.git] / util / src / TclTk / tk8.6.12 / generic / tkMenubutton.c
1 /*
2  * tkMenubutton.c --
3  *
4  *      This module implements button-like widgets that are used to invoke
5  *      pull-down menus.
6  *
7  * Copyright (c) 1990-1994 The Regents of the University of California.
8  * Copyright (c) 1994-1997 Sun Microsystems, Inc.
9  *
10  * See the file "license.terms" for information on usage and redistribution of
11  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  */
13
14 #include "tkInt.h"
15 #include "tkMenubutton.h"
16 #include "default.h"
17
18 /*
19  * The structure below defines menubutton class behavior by means of
20  * procedures that can be invoked from generic window code.
21  */
22
23 static const Tk_ClassProcs menubuttonClass = {
24     sizeof(Tk_ClassProcs),      /* size */
25     TkMenuButtonWorldChanged,   /* worldChangedProc */
26     NULL,                       /* createProc */
27     NULL                        /* modalProc */
28 };
29
30 /*
31  * The following table defines the legal values for the -direction option. It
32  * is used together with the "enum direction" declaration in tkMenubutton.h.
33  */
34
35 static const char *const directionStrings[] = {
36     "above", "below", "flush", "left", "right", NULL
37 };
38
39 /*
40  * The following table defines the legal values for the -state option. It is
41  * used together with the "enum state" declaration in tkMenubutton.h.
42  */
43
44 static const char *const stateStrings[] = {
45     "active", "disabled", "normal", NULL
46 };
47
48 /*
49  * The following table defines the legal values for the -compound option. It
50  * is used with the "enum compound" declaration in tkMenuButton.h
51  */
52
53 static const char *const compoundStrings[] = {
54     "bottom", "center", "left", "none", "right", "top", NULL
55 };
56
57 /*
58  * Information used for parsing configuration specs:
59  */
60
61 static const Tk_OptionSpec optionSpecs[] = {
62     {TK_OPTION_BORDER, "-activebackground", "activeBackground", "Foreground",
63         DEF_MENUBUTTON_ACTIVE_BG_COLOR, -1,
64         Tk_Offset(TkMenuButton, activeBorder), 0,
65         (ClientData) DEF_MENUBUTTON_ACTIVE_BG_MONO, 0},
66     {TK_OPTION_COLOR, "-activeforeground", "activeForeground", "Background",
67         DEF_MENUBUTTON_ACTIVE_FG_COLOR, -1,
68          Tk_Offset(TkMenuButton, activeFg),
69          0, DEF_MENUBUTTON_ACTIVE_FG_MONO, 0},
70     {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor",
71         DEF_MENUBUTTON_ANCHOR, -1,
72         Tk_Offset(TkMenuButton, anchor), 0, 0, 0},
73     {TK_OPTION_BORDER, "-background", "background", "Background",
74         DEF_MENUBUTTON_BG_COLOR, -1, Tk_Offset(TkMenuButton, normalBorder),
75         0, DEF_MENUBUTTON_BG_MONO, 0},
76     {TK_OPTION_SYNONYM, "-bd", NULL, NULL, NULL, 0, -1, 0,
77         (ClientData) "-borderwidth", 0},
78     {TK_OPTION_SYNONYM, "-bg", NULL, NULL, NULL, 0, -1, 0,
79         (ClientData) "-background", 0},
80     {TK_OPTION_BITMAP, "-bitmap", "bitmap", "Bitmap",
81         DEF_MENUBUTTON_BITMAP, -1, Tk_Offset(TkMenuButton, bitmap),
82         TK_OPTION_NULL_OK, 0, 0},
83     {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
84         DEF_MENUBUTTON_BORDER_WIDTH, -1,
85         Tk_Offset(TkMenuButton, borderWidth), 0, 0, 0},
86     {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
87         DEF_MENUBUTTON_CURSOR, -1, Tk_Offset(TkMenuButton, cursor),
88         TK_OPTION_NULL_OK, 0, 0},
89     {TK_OPTION_STRING_TABLE, "-direction", "direction", "Direction",
90         DEF_MENUBUTTON_DIRECTION, -1, Tk_Offset(TkMenuButton, direction),
91         0, directionStrings, 0},
92     {TK_OPTION_COLOR, "-disabledforeground", "disabledForeground",
93         "DisabledForeground", DEF_MENUBUTTON_DISABLED_FG_COLOR,
94         -1, Tk_Offset(TkMenuButton, disabledFg), TK_OPTION_NULL_OK,
95         (ClientData) DEF_MENUBUTTON_DISABLED_FG_MONO, 0},
96     {TK_OPTION_SYNONYM, "-fg", "foreground", NULL, NULL, 0, -1, 0,
97         (ClientData) "-foreground", 0},
98     {TK_OPTION_FONT, "-font", "font", "Font",
99         DEF_MENUBUTTON_FONT, -1, Tk_Offset(TkMenuButton, tkfont), 0, 0, 0},
100     {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
101         DEF_MENUBUTTON_FG, -1, Tk_Offset(TkMenuButton, normalFg), 0, 0, 0},
102     {TK_OPTION_STRING, "-height", "height", "Height",
103         DEF_MENUBUTTON_HEIGHT, -1, Tk_Offset(TkMenuButton, heightString),
104         0, 0, 0},
105     {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
106         "HighlightBackground", DEF_MENUBUTTON_HIGHLIGHT_BG_COLOR,
107         -1, Tk_Offset(TkMenuButton, highlightBgColorPtr), 0, 0, 0},
108     {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
109         DEF_MENUBUTTON_HIGHLIGHT, -1,
110         Tk_Offset(TkMenuButton, highlightColorPtr),     0, 0, 0},
111     {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
112         "HighlightThickness", DEF_MENUBUTTON_HIGHLIGHT_WIDTH,
113         -1, Tk_Offset(TkMenuButton, highlightWidth), 0, 0, 0},
114     {TK_OPTION_STRING, "-image", "image", "Image",
115         DEF_MENUBUTTON_IMAGE, -1, Tk_Offset(TkMenuButton, imageString),
116         TK_OPTION_NULL_OK, 0, 0},
117     {TK_OPTION_BOOLEAN, "-indicatoron", "indicatorOn", "IndicatorOn",
118         DEF_MENUBUTTON_INDICATOR, -1, Tk_Offset(TkMenuButton, indicatorOn),
119         0, 0, 0},
120     {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify",
121         DEF_MENUBUTTON_JUSTIFY, -1, Tk_Offset(TkMenuButton, justify), 0, 0, 0},
122     {TK_OPTION_STRING, "-menu", "menu", "Menu",
123         DEF_MENUBUTTON_MENU, -1, Tk_Offset(TkMenuButton, menuName),
124         TK_OPTION_NULL_OK, 0, 0},
125     {TK_OPTION_PIXELS, "-padx", "padX", "Pad",
126         DEF_MENUBUTTON_PADX, -1, Tk_Offset(TkMenuButton, padX),
127         0, 0, 0},
128     {TK_OPTION_PIXELS, "-pady", "padY", "Pad",
129         DEF_MENUBUTTON_PADY, -1, Tk_Offset(TkMenuButton, padY),
130         0, 0, 0},
131     {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
132         DEF_MENUBUTTON_RELIEF, -1, Tk_Offset(TkMenuButton, relief),
133         0, 0, 0},
134     {TK_OPTION_STRING_TABLE, "-compound", "compound", "Compound",
135          DEF_BUTTON_COMPOUND, -1, Tk_Offset(TkMenuButton, compound), 0,
136          compoundStrings, 0},
137     {TK_OPTION_STRING_TABLE, "-state", "state", "State",
138         DEF_MENUBUTTON_STATE, -1, Tk_Offset(TkMenuButton, state),
139         0, stateStrings, 0},
140     {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
141         DEF_MENUBUTTON_TAKE_FOCUS, -1,
142         Tk_Offset(TkMenuButton, takeFocus), TK_OPTION_NULL_OK, 0, 0},
143     {TK_OPTION_STRING, "-text", "text", "Text",
144         DEF_MENUBUTTON_TEXT, -1, Tk_Offset(TkMenuButton, text), 0, 0, 0},
145     {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable",
146         DEF_MENUBUTTON_TEXT_VARIABLE, -1,
147         Tk_Offset(TkMenuButton, textVarName), TK_OPTION_NULL_OK, 0, 0},
148     {TK_OPTION_INT, "-underline", "underline", "Underline",
149         DEF_MENUBUTTON_UNDERLINE, -1, Tk_Offset(TkMenuButton, underline),
150          0, 0, 0},
151     {TK_OPTION_STRING, "-width", "width", "Width",
152         DEF_MENUBUTTON_WIDTH, -1, Tk_Offset(TkMenuButton, widthString),
153         0, 0, 0},
154     {TK_OPTION_PIXELS, "-wraplength", "wrapLength", "WrapLength",
155         DEF_MENUBUTTON_WRAP_LENGTH, -1, Tk_Offset(TkMenuButton, wrapLength),
156         0, 0, 0},
157     {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0}
158 };
159
160 /*
161  * The following tables define the menubutton widget commands and map the
162  * indexes into the string tables into a single enumerated type used to
163  * dispatch the scale widget command.
164  */
165
166 static const char *const commandNames[] = {
167     "cget", "configure", NULL
168 };
169
170 enum command {
171     COMMAND_CGET, COMMAND_CONFIGURE
172 };
173
174 /*
175  * Forward declarations for functions defined later in this file:
176  */
177
178 static void             MenuButtonCmdDeletedProc(ClientData clientData);
179 static void             MenuButtonEventProc(ClientData clientData,
180                             XEvent *eventPtr);
181 static void             MenuButtonImageProc(ClientData clientData,
182                             int x, int y, int width, int height, int imgWidth,
183                             int imgHeight);
184 static char *           MenuButtonTextVarProc(ClientData clientData,
185                             Tcl_Interp *interp, const char *name1,
186                             const char *name2, int flags);
187 static int              MenuButtonWidgetObjCmd(ClientData clientData,
188                             Tcl_Interp *interp, int objc,
189                             Tcl_Obj *const objv[]);
190 static int              ConfigureMenuButton(Tcl_Interp *interp,
191                             TkMenuButton *mbPtr, int objc,
192                             Tcl_Obj *const objv[]);
193 static void             DestroyMenuButton(char *memPtr);
194 \f
195 /*
196  *--------------------------------------------------------------
197  *
198  * Tk_MenubuttonObjCmd --
199  *
200  *      This function is invoked to process the "button", "label",
201  *      "radiobutton", and "checkbutton" Tcl commands. See the user
202  *      documentation for details on what it does.
203  *
204  * Results:
205  *      A standard Tcl result.
206  *
207  * Side effects:
208  *      See the user documentation.
209  *
210  *--------------------------------------------------------------
211  */
212
213 int
214 Tk_MenubuttonObjCmd(
215     ClientData clientData,      /* NULL. */
216     Tcl_Interp *interp,         /* Current interpreter. */
217     int objc,                   /* Number of arguments. */
218     Tcl_Obj *const objv[])      /* Argument objects. */
219 {
220     TkMenuButton *mbPtr;
221     Tk_OptionTable optionTable;
222     Tk_Window tkwin;
223
224     if (objc < 2) {
225         Tcl_WrongNumArgs(interp, 1, objv, "pathName ?-option value ...?");
226         return TCL_ERROR;
227     }
228
229     /*
230      * Create the new window.
231      */
232
233     tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
234             Tcl_GetString(objv[1]), NULL);
235     if (tkwin == NULL) {
236         return TCL_ERROR;
237     }
238
239     /*
240      * Create the option table for this widget class. If it has already been
241      * created, the cached pointer will be returned.
242      */
243
244     optionTable = Tk_CreateOptionTable(interp, optionSpecs);
245
246     Tk_SetClass(tkwin, "Menubutton");
247     mbPtr = TkpCreateMenuButton(tkwin);
248
249     Tk_SetClassProcs(tkwin, &menubuttonClass, mbPtr);
250
251     /*
252      * Initialize the data structure for the button.
253      */
254
255     mbPtr->tkwin = tkwin;
256     mbPtr->display = Tk_Display(tkwin);
257     mbPtr->interp = interp;
258     mbPtr->widgetCmd = Tcl_CreateObjCommand(interp,
259             Tk_PathName(mbPtr->tkwin), MenuButtonWidgetObjCmd, mbPtr,
260             MenuButtonCmdDeletedProc);
261     mbPtr->optionTable = optionTable;
262     mbPtr->menuName = NULL;
263     mbPtr->text = NULL;
264     mbPtr->underline = -1;
265     mbPtr->textVarName = NULL;
266     mbPtr->bitmap = None;
267     mbPtr->imageString = NULL;
268     mbPtr->image = NULL;
269     mbPtr->state = STATE_NORMAL;
270     mbPtr->normalBorder = NULL;
271     mbPtr->activeBorder = NULL;
272     mbPtr->borderWidth = 0;
273     mbPtr->relief = TK_RELIEF_FLAT;
274     mbPtr->highlightWidth = 0;
275     mbPtr->highlightBgColorPtr = NULL;
276     mbPtr->highlightColorPtr = NULL;
277     mbPtr->inset = 0;
278     mbPtr->tkfont = NULL;
279     mbPtr->normalFg = NULL;
280     mbPtr->activeFg = NULL;
281     mbPtr->disabledFg = NULL;
282     mbPtr->normalTextGC = NULL;
283     mbPtr->activeTextGC = NULL;
284     mbPtr->gray = None;
285     mbPtr->disabledGC = NULL;
286     mbPtr->stippleGC = NULL;
287     mbPtr->leftBearing = 0;
288     mbPtr->rightBearing = 0;
289     mbPtr->widthString = NULL;
290     mbPtr->heightString = NULL;
291     mbPtr->width = 0;
292     mbPtr->width = 0;
293     mbPtr->wrapLength = 0;
294     mbPtr->padX = 0;
295     mbPtr->padY = 0;
296     mbPtr->anchor = TK_ANCHOR_CENTER;
297     mbPtr->justify = TK_JUSTIFY_CENTER;
298     mbPtr->textLayout = NULL;
299     mbPtr->indicatorOn = 0;
300     mbPtr->indicatorWidth = 0;
301     mbPtr->indicatorHeight = 0;
302     mbPtr->direction = DIRECTION_FLUSH;
303     mbPtr->cursor = NULL;
304     mbPtr->takeFocus = NULL;
305     mbPtr->flags = 0;
306
307     Tk_CreateEventHandler(mbPtr->tkwin,
308             ExposureMask|StructureNotifyMask|FocusChangeMask,
309             MenuButtonEventProc, mbPtr);
310
311     if (Tk_InitOptions(interp, (char *) mbPtr, optionTable, tkwin) != TCL_OK) {
312         Tk_DestroyWindow(mbPtr->tkwin);
313         return TCL_ERROR;
314     }
315
316     if (ConfigureMenuButton(interp, mbPtr, objc-2, objv+2) != TCL_OK) {
317         Tk_DestroyWindow(mbPtr->tkwin);
318         return TCL_ERROR;
319     }
320
321     Tcl_SetObjResult(interp, TkNewWindowObj(mbPtr->tkwin));
322     return TCL_OK;
323 }
324 \f
325 /*
326  *--------------------------------------------------------------
327  *
328  * MenuButtonWidgetObjCmd --
329  *
330  *      This function is invoked to process the Tcl command that corresponds
331  *      to a widget managed by this module. See the user documentation for
332  *      details on what it does.
333  *
334  * Results:
335  *      A standard Tcl result.
336  *
337  * Side effects:
338  *      See the user documentation.
339  *
340  *--------------------------------------------------------------
341  */
342
343 static int
344 MenuButtonWidgetObjCmd(
345     ClientData clientData,      /* Information about button widget. */
346     Tcl_Interp *interp,         /* Current interpreter. */
347     int objc,                   /* Number of arguments. */
348     Tcl_Obj *const objv[])      /* Argument objects. */
349 {
350     TkMenuButton *mbPtr = clientData;
351     int result, index;
352     Tcl_Obj *objPtr;
353
354     if (objc < 2) {
355         Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
356         return TCL_ERROR;
357     }
358     result = Tcl_GetIndexFromObjStruct(interp, objv[1], commandNames,
359             sizeof(char *), "option", 0, &index);
360     if (result != TCL_OK) {
361         return result;
362     }
363     Tcl_Preserve(mbPtr);
364
365     switch (index) {
366     case COMMAND_CGET:
367         if (objc != 3) {
368             Tcl_WrongNumArgs(interp, 1, objv, "cget option");
369             goto error;
370         }
371
372         objPtr = Tk_GetOptionValue(interp, (char *) mbPtr,
373                 mbPtr->optionTable, objv[2], mbPtr->tkwin);
374         if (objPtr == NULL) {
375             goto error;
376         }
377         Tcl_SetObjResult(interp, objPtr);
378         break;
379
380     case COMMAND_CONFIGURE:
381         if (objc <= 3) {
382             objPtr = Tk_GetOptionInfo(interp, (char *) mbPtr,
383                     mbPtr->optionTable, (objc == 3) ? objv[2] : NULL,
384                     mbPtr->tkwin);
385             if (objPtr == NULL) {
386                 goto error;
387             }
388             Tcl_SetObjResult(interp, objPtr);
389         } else {
390             result = ConfigureMenuButton(interp, mbPtr, objc-2, objv+2);
391         }
392         break;
393     }
394     Tcl_Release(mbPtr);
395     return result;
396
397   error:
398     Tcl_Release(mbPtr);
399     return TCL_ERROR;
400 }
401 \f
402 /*
403  *----------------------------------------------------------------------
404  *
405  * DestroyMenuButton --
406  *
407  *      This function is invoked to recycle all of the resources associated
408  *      with a menubutton widget. It is invoked as a when-idle handler in
409  *      order to make sure that there is no other use of the menubutton
410  *      pending at the time of the deletion.
411  *
412  * Results:
413  *      None.
414  *
415  * Side effects:
416  *      Everything associated with the widget is freed up.
417  *
418  *----------------------------------------------------------------------
419  */
420
421 static void
422 DestroyMenuButton(
423     char *memPtr)               /* Info about button widget. */
424 {
425     TkMenuButton *mbPtr = (TkMenuButton *) memPtr;
426     TkpDestroyMenuButton(mbPtr);
427
428     if (mbPtr->flags & REDRAW_PENDING) {
429         Tcl_CancelIdleCall(TkpDisplayMenuButton, mbPtr);
430     }
431
432     /*
433      * Free up all the stuff that requires special handling, then let
434      * Tk_FreeOptions handle all the standard option-related stuff.
435      */
436
437     Tcl_DeleteCommandFromToken(mbPtr->interp, mbPtr->widgetCmd);
438     if (mbPtr->textVarName != NULL) {
439         Tcl_UntraceVar2(mbPtr->interp, mbPtr->textVarName, NULL,
440                 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
441                 MenuButtonTextVarProc, mbPtr);
442     }
443     if (mbPtr->image != NULL) {
444         Tk_FreeImage(mbPtr->image);
445     }
446     if (mbPtr->normalTextGC != NULL) {
447         Tk_FreeGC(mbPtr->display, mbPtr->normalTextGC);
448     }
449     if (mbPtr->activeTextGC != NULL) {
450         Tk_FreeGC(mbPtr->display, mbPtr->activeTextGC);
451     }
452     if (mbPtr->disabledGC != NULL) {
453         Tk_FreeGC(mbPtr->display, mbPtr->disabledGC);
454     }
455     if (mbPtr->stippleGC != NULL) {
456         Tk_FreeGC(mbPtr->display, mbPtr->stippleGC);
457     }
458     if (mbPtr->gray != None) {
459         Tk_FreeBitmap(mbPtr->display, mbPtr->gray);
460     }
461     if (mbPtr->textLayout != NULL) {
462         Tk_FreeTextLayout(mbPtr->textLayout);
463     }
464     Tk_FreeConfigOptions((char *) mbPtr, mbPtr->optionTable, mbPtr->tkwin);
465     mbPtr->tkwin = NULL;
466     Tcl_EventuallyFree(mbPtr, TCL_DYNAMIC);
467 }
468 \f
469 /*
470  *----------------------------------------------------------------------
471  *
472  * ConfigureMenuButton --
473  *
474  *      This function is called to process an argv/argc list, plus the Tk
475  *      option database, in order to configure (or reconfigure) a menubutton
476  *      widget.
477  *
478  * Results:
479  *      The return value is a standard Tcl result. If TCL_ERROR is returned,
480  *      then the interp's result contains an error message.
481  *
482  * Side effects:
483  *      Configuration information, such as text string, colors, font, etc. get
484  *      set for mbPtr; old resources get freed, if there were any. The
485  *      menubutton is redisplayed.
486  *
487  *----------------------------------------------------------------------
488  */
489
490 static int
491 ConfigureMenuButton(
492     Tcl_Interp *interp,         /* Used for error reporting. */
493     TkMenuButton *mbPtr,
494                                 /* Information about widget; may or may not
495                                  * already have values for some fields. */
496     int objc,                   /* Number of valid entries in objv. */
497     Tcl_Obj *const objv[])      /* Arguments. */
498 {
499     Tk_SavedOptions savedOptions;
500     Tcl_Obj *errorResult = NULL;
501     int error;
502     Tk_Image image;
503
504     /*
505      * Eliminate any existing trace on variables monitored by the menubutton.
506      */
507
508     if (mbPtr->textVarName != NULL) {
509         Tcl_UntraceVar2(interp, mbPtr->textVarName, NULL,
510                 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
511                 MenuButtonTextVarProc, mbPtr);
512     }
513
514     /*
515      * The following loop is potentially executed twice. During the first pass
516      * configuration options get set to their new values. If there is an error
517      * in this pass, we execute a second pass to restore all the options to
518      * their previous values.
519      */
520
521     for (error = 0; error <= 1; error++) {
522         if (!error) {
523             /*
524              * First pass: set options to new values.
525              */
526
527             if (Tk_SetOptions(interp, (char *) mbPtr,
528                     mbPtr->optionTable, objc, objv,
529                     mbPtr->tkwin, &savedOptions, NULL) != TCL_OK) {
530                 continue;
531             }
532         } else {
533             /*
534              * Second pass: restore options to old values.
535              */
536
537             errorResult = Tcl_GetObjResult(interp);
538             Tcl_IncrRefCount(errorResult);
539             Tk_RestoreSavedOptions(&savedOptions);
540         }
541
542         /*
543          * A few options need special processing, such as setting the
544          * background from a 3-D border, or filling in complicated defaults
545          * that couldn't be specified to Tk_SetOptions.
546          */
547
548         if ((mbPtr->state == STATE_ACTIVE)
549                 && !Tk_StrictMotif(mbPtr->tkwin)) {
550             Tk_SetBackgroundFromBorder(mbPtr->tkwin, mbPtr->activeBorder);
551         } else {
552             Tk_SetBackgroundFromBorder(mbPtr->tkwin, mbPtr->normalBorder);
553         }
554
555         if (mbPtr->highlightWidth < 0) {
556             mbPtr->highlightWidth = 0;
557         }
558
559         if (mbPtr->padX < 0) {
560             mbPtr->padX = 0;
561         }
562         if (mbPtr->padY < 0) {
563             mbPtr->padY = 0;
564         }
565
566         /*
567          * Get the image for the widget, if there is one. Allocate the new
568          * image before freeing the old one, so that the reference count
569          * doesn't go to zero and cause image data to be discarded.
570          */
571
572         if (mbPtr->imageString != NULL) {
573             image = Tk_GetImage(mbPtr->interp, mbPtr->tkwin,
574                     mbPtr->imageString, MenuButtonImageProc, mbPtr);
575             if (image == NULL) {
576                 return TCL_ERROR;
577             }
578         } else {
579             image = NULL;
580         }
581         if (mbPtr->image != NULL) {
582             Tk_FreeImage(mbPtr->image);
583         }
584         mbPtr->image = image;
585
586         /*
587          * Recompute the geometry for the button.
588          */
589
590         if ((mbPtr->bitmap != None) || (mbPtr->image != NULL)) {
591             if (Tk_GetPixels(interp, mbPtr->tkwin, mbPtr->widthString,
592                     &mbPtr->width) != TCL_OK) {
593             widthError:
594                 Tcl_AddErrorInfo(interp, "\n    (processing -width option)");
595                 continue;
596             }
597             if (Tk_GetPixels(interp, mbPtr->tkwin, mbPtr->heightString,
598                     &mbPtr->height) != TCL_OK) {
599             heightError:
600                 Tcl_AddErrorInfo(interp, "\n    (processing -height option)");
601                 continue;
602             }
603         } else {
604             if (Tcl_GetInt(interp, mbPtr->widthString, &mbPtr->width)
605                     != TCL_OK) {
606                 goto widthError;
607             }
608             if (Tcl_GetInt(interp, mbPtr->heightString, &mbPtr->height)
609                     != TCL_OK) {
610                 goto heightError;
611             }
612         }
613         break;
614     }
615
616     if (!error) {
617         Tk_FreeSavedOptions(&savedOptions);
618     }
619
620     if (mbPtr->textVarName != NULL) {
621         /*
622          * If no image or -compound is used, display the value of a variable.
623          * Set up a trace to watch for any changes in it, create the variable
624          * if it doesn't exist, and fetch its current value.
625          */
626         const char *value;
627
628         value = Tcl_GetVar2(interp, mbPtr->textVarName, NULL, TCL_GLOBAL_ONLY);
629         if (value == NULL) {
630             Tcl_SetVar2(interp, mbPtr->textVarName, NULL, mbPtr->text,
631                     TCL_GLOBAL_ONLY);
632         } else {
633             if (mbPtr->text != NULL) {
634                 ckfree(mbPtr->text);
635             }
636             mbPtr->text = ckalloc(strlen(value) + 1);
637             strcpy(mbPtr->text, value);
638         }
639         Tcl_TraceVar2(interp, mbPtr->textVarName, NULL,
640                 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
641                 MenuButtonTextVarProc, mbPtr);
642     }
643
644     TkMenuButtonWorldChanged(mbPtr);
645     if (error) {
646         Tcl_SetObjResult(interp, errorResult);
647         Tcl_DecrRefCount(errorResult);
648         return TCL_ERROR;
649     }
650     return TCL_OK;
651 }
652 \f
653 /*
654  *---------------------------------------------------------------------------
655  *
656  * TkMenuButtonWorldChanged --
657  *
658  *      This function is called when the world has changed in some way and the
659  *      widget needs to recompute all its graphics contexts and determine its
660  *      new geometry.
661  *
662  * Results:
663  *      None.
664  *
665  * Side effects:
666  *      TkMenuButton will be relayed out and redisplayed.
667  *
668  *---------------------------------------------------------------------------
669  */
670
671 void
672 TkMenuButtonWorldChanged(
673     ClientData instanceData)    /* Information about widget. */
674 {
675     XGCValues gcValues;
676     GC gc;
677     unsigned long mask;
678     TkMenuButton *mbPtr = instanceData;
679
680     gcValues.font = Tk_FontId(mbPtr->tkfont);
681     gcValues.foreground = mbPtr->normalFg->pixel;
682     gcValues.background = Tk_3DBorderColor(mbPtr->normalBorder)->pixel;
683
684     /*
685      * Note: GraphicsExpose events are disabled in GC's because they're used
686      * to copy stuff from an off-screen pixmap onto the screen (we know that
687      * there's no problem with obscured areas).
688      */
689
690     gcValues.graphics_exposures = False;
691     mask = GCForeground | GCBackground | GCFont | GCGraphicsExposures;
692     gc = Tk_GetGC(mbPtr->tkwin, mask, &gcValues);
693     if (mbPtr->normalTextGC != NULL) {
694         Tk_FreeGC(mbPtr->display, mbPtr->normalTextGC);
695     }
696     mbPtr->normalTextGC = gc;
697
698     gcValues.foreground = mbPtr->activeFg->pixel;
699     gcValues.background = Tk_3DBorderColor(mbPtr->activeBorder)->pixel;
700     mask = GCForeground | GCBackground | GCFont;
701     gc = Tk_GetGC(mbPtr->tkwin, mask, &gcValues);
702     if (mbPtr->activeTextGC != NULL) {
703         Tk_FreeGC(mbPtr->display, mbPtr->activeTextGC);
704     }
705     mbPtr->activeTextGC = gc;
706
707     gcValues.background = Tk_3DBorderColor(mbPtr->normalBorder)->pixel;
708
709     /*
710      * Create the GC that can be used for stippling
711      */
712
713     if (mbPtr->stippleGC == NULL) {
714         gcValues.foreground = gcValues.background;
715         mask = GCForeground;
716         if (mbPtr->gray == None) {
717             mbPtr->gray = Tk_GetBitmap(NULL, mbPtr->tkwin, "gray50");
718         }
719         if (mbPtr->gray != None) {
720             gcValues.fill_style = FillStippled;
721             gcValues.stipple = mbPtr->gray;
722             mask |= GCFillStyle | GCStipple;
723         }
724         mbPtr->stippleGC = Tk_GetGC(mbPtr->tkwin, mask, &gcValues);
725     }
726
727     /*
728      * Allocate the disabled graphics context, for drawing text in its
729      * disabled state.
730      */
731
732     mask = GCForeground | GCBackground | GCFont;
733     if (mbPtr->disabledFg != NULL) {
734         gcValues.foreground = mbPtr->disabledFg->pixel;
735     } else {
736         gcValues.foreground = gcValues.background;
737     }
738     gc = Tk_GetGC(mbPtr->tkwin, mask, &gcValues);
739     if (mbPtr->disabledGC != NULL) {
740         Tk_FreeGC(mbPtr->display, mbPtr->disabledGC);
741     }
742     mbPtr->disabledGC = gc;
743
744     TkpComputeMenuButtonGeometry(mbPtr);
745
746     /*
747      * Lastly, arrange for the button to be redisplayed.
748      */
749
750     if (Tk_IsMapped(mbPtr->tkwin) && !(mbPtr->flags & REDRAW_PENDING)) {
751         Tcl_DoWhenIdle(TkpDisplayMenuButton, mbPtr);
752         mbPtr->flags |= REDRAW_PENDING;
753     }
754 }
755 \f
756 /*
757  *--------------------------------------------------------------
758  *
759  * MenuButtonEventProc --
760  *
761  *      This function is invoked by the Tk dispatcher for various events on
762  *      buttons.
763  *
764  * Results:
765  *      None.
766  *
767  * Side effects:
768  *      When the window gets deleted, internal structures get cleaned up.
769  *      When it gets exposed, it is redisplayed.
770  *
771  *--------------------------------------------------------------
772  */
773
774 static void
775 MenuButtonEventProc(
776     ClientData clientData,      /* Information about window. */
777     XEvent *eventPtr)           /* Information about event. */
778 {
779     TkMenuButton *mbPtr = clientData;
780
781     if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
782         goto redraw;
783     } else if (eventPtr->type == ConfigureNotify) {
784         /*
785          * Must redraw after size changes, since layout could have changed and
786          * borders will need to be redrawn.
787          */
788
789         goto redraw;
790     } else if (eventPtr->type == DestroyNotify) {
791         DestroyMenuButton((char *) mbPtr);
792     } else if (eventPtr->type == FocusIn) {
793         if (eventPtr->xfocus.detail != NotifyInferior) {
794             mbPtr->flags |= GOT_FOCUS;
795             if (mbPtr->highlightWidth > 0) {
796                 goto redraw;
797             }
798         }
799     } else if (eventPtr->type == FocusOut) {
800         if (eventPtr->xfocus.detail != NotifyInferior) {
801             mbPtr->flags &= ~GOT_FOCUS;
802             if (mbPtr->highlightWidth > 0) {
803                 goto redraw;
804             }
805         }
806     }
807     return;
808
809   redraw:
810     if ((mbPtr->tkwin != NULL) && !(mbPtr->flags & REDRAW_PENDING)) {
811         Tcl_DoWhenIdle(TkpDisplayMenuButton, mbPtr);
812         mbPtr->flags |= REDRAW_PENDING;
813     }
814 }
815 \f
816 /*
817  *----------------------------------------------------------------------
818  *
819  * MenuButtonCmdDeletedProc --
820  *
821  *      This function is invoked when a widget command is deleted. If the
822  *      widget isn't already in the process of being destroyed, this command
823  *      destroys it.
824  *
825  * Results:
826  *      None.
827  *
828  * Side effects:
829  *      The widget is destroyed.
830  *
831  *----------------------------------------------------------------------
832  */
833
834 static void
835 MenuButtonCmdDeletedProc(
836     ClientData clientData)      /* Pointer to widget record for widget. */
837 {
838     TkMenuButton *mbPtr = clientData;
839     Tk_Window tkwin = mbPtr->tkwin;
840
841     /*
842      * This function could be invoked either because the window was destroyed
843      * and the command was then deleted (in which case tkwin is NULL) or
844      * because the command was deleted, and then this function destroys the
845      * widget.
846      */
847
848     if (tkwin != NULL) {
849         Tk_DestroyWindow(tkwin);
850     }
851 }
852 \f
853 /*
854  *--------------------------------------------------------------
855  *
856  * MenuButtonTextVarProc --
857  *
858  *      This function is invoked when someone changes the variable whose
859  *      contents are to be displayed in a menu button.
860  *
861  * Results:
862  *      NULL is always returned.
863  *
864  * Side effects:
865  *      The text displayed in the menu button will change to match the
866  *      variable.
867  *
868  *--------------------------------------------------------------
869  */
870
871         /* ARGSUSED */
872 static char *
873 MenuButtonTextVarProc(
874     ClientData clientData,      /* Information about button. */
875     Tcl_Interp *interp,         /* Interpreter containing variable. */
876     const char *name1,          /* Name of variable. */
877     const char *name2,          /* Second part of variable name. */
878     int flags)                  /* Information about what happened. */
879 {
880     TkMenuButton *mbPtr = clientData;
881     const char *value;
882     unsigned len;
883
884     /*
885      * If the variable is unset, then immediately recreate it unless the whole
886      * interpreter is going away.
887      */
888
889     if (flags & TCL_TRACE_UNSETS) {
890         if (!Tcl_InterpDeleted(interp) && mbPtr->textVarName) {
891             ClientData probe = NULL;
892
893             do {
894                 probe = Tcl_VarTraceInfo(interp,
895                         mbPtr->textVarName,
896                         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
897                         MenuButtonTextVarProc, probe);
898                 if (probe == (ClientData)mbPtr) {
899                     break;
900                 }
901             } while (probe);
902             if (probe) {
903                 /*
904                  * We were able to fetch the unset trace for our
905                  * textVarName, which means it is not unset and not
906                  * the cause of this unset trace. Instead some outdated
907                  * former variable must be, and we should ignore it.
908                  */
909                 return NULL;
910             }
911             Tcl_SetVar2(interp, mbPtr->textVarName, NULL, mbPtr->text,
912                     TCL_GLOBAL_ONLY);
913             Tcl_TraceVar2(interp, mbPtr->textVarName, NULL,
914                     TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
915                     MenuButtonTextVarProc, clientData);
916         }
917         return NULL;
918     }
919
920     value = Tcl_GetVar2(interp, mbPtr->textVarName, NULL, TCL_GLOBAL_ONLY);
921     if (value == NULL) {
922         value = "";
923     }
924     if (mbPtr->text != NULL) {
925         ckfree(mbPtr->text);
926     }
927     len = 1 + (unsigned) strlen(value);
928     mbPtr->text = ckalloc(len);
929     memcpy(mbPtr->text, value, len);
930     TkpComputeMenuButtonGeometry(mbPtr);
931
932     if ((mbPtr->tkwin != NULL) && Tk_IsMapped(mbPtr->tkwin)
933             && !(mbPtr->flags & REDRAW_PENDING)) {
934         Tcl_DoWhenIdle(TkpDisplayMenuButton, mbPtr);
935         mbPtr->flags |= REDRAW_PENDING;
936     }
937     return NULL;
938 }
939 \f
940 /*
941  *----------------------------------------------------------------------
942  *
943  * MenuButtonImageProc --
944  *
945  *      This function is invoked by the image code whenever the manager for an
946  *      image does something that affects the size of contents of an image
947  *      displayed in a button.
948  *
949  * Results:
950  *      None.
951  *
952  * Side effects:
953  *      Arranges for the button to get redisplayed.
954  *
955  *----------------------------------------------------------------------
956  */
957
958 static void
959 MenuButtonImageProc(
960     ClientData clientData,      /* Pointer to widget record. */
961     int x, int y,               /* Upper left pixel (within image) that must
962                                  * be redisplayed. */
963     int width, int height,      /* Dimensions of area to redisplay (may be <=
964                                  * 0). */
965     int imgWidth, int imgHeight)/* New dimensions of image. */
966 {
967     TkMenuButton *mbPtr = clientData;
968
969     if (mbPtr->tkwin != NULL) {
970         TkpComputeMenuButtonGeometry(mbPtr);
971         if (Tk_IsMapped(mbPtr->tkwin) && !(mbPtr->flags & REDRAW_PENDING)) {
972             Tcl_DoWhenIdle(TkpDisplayMenuButton, mbPtr);
973             mbPtr->flags |= REDRAW_PENDING;
974         }
975     }
976 }
977 \f
978 /*
979  * Local Variables:
980  * mode: c
981  * c-basic-offset: 4
982  * fill-column: 78
983  * End:
984  */