4 * This module implements button-like widgets that are used
5 * to invoke pull-down menus.
7 * Copyright 1990 Regents of the University of California.
8 * Permission to use, copy, modify, and distribute this
9 * software and its documentation for any purpose and without
10 * fee is hereby granted, provided that the above copyright
11 * notice appear in all copies. The University of California
12 * makes no representations about the suitability of this
13 * software for any purpose. It is provided "as is" without
14 * express or implied warranty.
18 static char rcsid
[] = "$Header: /user6/ouster/wish/RCS/tkMenubutton.c,v 1.33 92/08/21 16:21:47 ouster Exp $ SPRITE (Berkeley)";
26 * A data structure of the following type is kept for each
27 * widget managed by this file:
31 Tk_Window tkwin
; /* Window that embodies the widget. NULL
32 * means that the window has been destroyed
33 * but the data structures haven't yet been
35 Tcl_Interp
*interp
; /* Interpreter associated with menu button. */
36 char *menuName
; /* Name of menu associated with widget (used
37 * to generate post and unpost commands).
39 Tk_Uid varName
; /* Name of variable associated with collection
40 * of menu bars: used to allow scanning of
41 * menus. Also used as identifier for
45 * Information about what's displayed in the menu button:
48 char *text
; /* Text to display in button (malloc'ed)
50 int textLength
; /* # of characters in text. */
51 int underline
; /* Index of character to underline. */
52 char *textVarName
; /* Name of variable (malloc'ed) or NULL.
53 * If non-NULL, button displays the contents
54 * of this variable. */
55 Pixmap bitmap
; /* Bitmap to display or None. If not None
56 * then text and textVar and underline
60 * Information used when displaying widget:
63 Tk_Uid state
; /* State of button for display purposes:
64 * normal, active, or disabled. */
65 Tk_3DBorder normalBorder
; /* Structure used to draw 3-D
66 * border and background when window
67 * isn't active. NULL means no such
69 Tk_3DBorder activeBorder
; /* Structure used to draw 3-D
70 * border and background when window
71 * is active. NULL means no such
73 int borderWidth
; /* Width of border. */
74 int relief
; /* 3-d effect: TK_RELIEF_RAISED, etc. */
75 XFontStruct
*fontPtr
; /* Information about text font, or NULL. */
76 XColor
*normalFg
; /* Foreground color in normal mode. */
77 XColor
*activeFg
; /* Foreground color in active mode. NULL
78 * means use normalFg instead. */
79 XColor
*disabledFg
; /* Foreground color when disabled. NULL
80 * means use normalFg with a 50% stipple
82 GC normalTextGC
; /* GC for drawing text in normal mode. */
83 GC activeTextGC
; /* GC for drawing text in active mode (NULL
84 * means use normalTextGC). */
85 Pixmap gray
; /* Pixmap for displaying disabled text/icon if
86 * disabledFg is NULL. */
87 GC disabledGC
; /* Used to produce disabled effect. If
88 * disabledFg isn't NULL, this GC is used to
89 * draw button text or icon. Otherwise
90 * text or icon is drawn with normalGC and
91 * this GC is used to stipple background
93 int leftBearing
; /* Amount text sticks left from its origin,
95 int rightBearing
; /* Amount text sticks right from its origin. */
96 int width
, height
; /* If > 0, these specify dimensions to request
97 * for window, in characters for text and in
98 * pixels for bitmaps. In this case the actual
99 * size of the text string or bitmap is
100 * ignored in computing desired window size. */
101 int padX
, padY
; /* Extra space around text or bitmap (pixels
103 Tk_Anchor anchor
; /* Where text/bitmap should be displayed
104 * inside window region. */
107 * Miscellaneous information:
110 Cursor cursor
; /* Current cursor for window, or None. */
111 int flags
; /* Various flags; see below for
116 * Flag bits for buttons:
118 * REDRAW_PENDING: Non-zero means a DoWhenIdle handler
119 * has already been queued to redraw
121 * POSTED: Non-zero means that the menu associated
122 * with this button has been posted (typically
123 * because of an active button press).
126 #define REDRAW_PENDING 1
130 * Information used for parsing configuration specs:
133 static Tk_ConfigSpec configSpecs
[] = {
134 {TK_CONFIG_BORDER
, "-activebackground", "activeBackground", "Foreground",
135 DEF_MENUBUTTON_ACTIVE_BG_COLOR
, Tk_Offset(MenuButton
, activeBorder
),
136 TK_CONFIG_COLOR_ONLY
},
137 {TK_CONFIG_BORDER
, "-activebackground", "activeBackground", "Foreground",
138 DEF_MENUBUTTON_ACTIVE_BG_MONO
, Tk_Offset(MenuButton
, activeBorder
),
139 TK_CONFIG_MONO_ONLY
},
140 {TK_CONFIG_COLOR
, "-activeforeground", "activeForeground", "Background",
141 DEF_MENUBUTTON_ACTIVE_FG_COLOR
, Tk_Offset(MenuButton
, activeFg
),
142 TK_CONFIG_COLOR_ONLY
},
143 {TK_CONFIG_COLOR
, "-activeforeground", "activeForeground", "Background",
144 DEF_MENUBUTTON_ACTIVE_FG_MONO
, Tk_Offset(MenuButton
, activeFg
),
145 TK_CONFIG_MONO_ONLY
},
146 {TK_CONFIG_ANCHOR
, "-anchor", "anchor", "Anchor",
147 DEF_MENUBUTTON_ANCHOR
, Tk_Offset(MenuButton
, anchor
), 0},
148 {TK_CONFIG_BORDER
, "-background", "background", "Background",
149 DEF_MENUBUTTON_BG_COLOR
, Tk_Offset(MenuButton
, normalBorder
),
150 TK_CONFIG_COLOR_ONLY
},
151 {TK_CONFIG_BORDER
, "-background", "background", "Background",
152 DEF_MENUBUTTON_BG_MONO
, Tk_Offset(MenuButton
, normalBorder
),
153 TK_CONFIG_MONO_ONLY
},
154 {TK_CONFIG_SYNONYM
, "-bd", "borderWidth", (char *) NULL
,
155 (char *) NULL
, 0, 0},
156 {TK_CONFIG_SYNONYM
, "-bg", "background", (char *) NULL
,
157 (char *) NULL
, 0, 0},
158 #if defined(USE_XPM3)
159 {TK_CONFIG_PIXMAP
, "-bitmap", "bitmap", "Bitmap",
160 DEF_MENUBUTTON_BITMAP
, Tk_Offset(MenuButton
, bitmap
),
163 {TK_CONFIG_BITMAP
, "-bitmap", "bitmap", "Bitmap",
164 DEF_MENUBUTTON_BITMAP
, Tk_Offset(MenuButton
, bitmap
),
167 {TK_CONFIG_PIXELS
, "-borderwidth", "borderWidth", "BorderWidth",
168 DEF_MENUBUTTON_BORDER_WIDTH
, Tk_Offset(MenuButton
, borderWidth
), 0},
169 {TK_CONFIG_ACTIVE_CURSOR
, "-cursor", "cursor", "Cursor",
170 DEF_MENUBUTTON_CURSOR
, Tk_Offset(MenuButton
, cursor
),
172 {TK_CONFIG_COLOR
, "-disabledforeground", "disabledForeground",
173 "DisabledForeground", DEF_MENUBUTTON_DISABLED_FG_COLOR
,
174 Tk_Offset(MenuButton
, disabledFg
),
175 TK_CONFIG_COLOR_ONLY
|TK_CONFIG_NULL_OK
},
176 {TK_CONFIG_COLOR
, "-disabledforeground", "disabledForeground",
177 "DisabledForeground", DEF_MENUBUTTON_DISABLED_FG_MONO
,
178 Tk_Offset(MenuButton
, disabledFg
),
179 TK_CONFIG_MONO_ONLY
|TK_CONFIG_NULL_OK
},
180 {TK_CONFIG_SYNONYM
, "-fg", "foreground", (char *) NULL
,
181 (char *) NULL
, 0, 0},
182 {TK_CONFIG_FONT
, "-font", "font", "Font",
183 DEF_MENUBUTTON_FONT
, Tk_Offset(MenuButton
, fontPtr
), 0},
184 {TK_CONFIG_COLOR
, "-foreground", "foreground", "Foreground",
185 DEF_MENUBUTTON_FG
, Tk_Offset(MenuButton
, normalFg
), 0},
186 {TK_CONFIG_INT
, "-height", "height", "Height",
187 DEF_MENUBUTTON_HEIGHT
, Tk_Offset(MenuButton
, height
), 0},
188 {TK_CONFIG_STRING
, "-menu", "menu", "Menu",
189 DEF_MENUBUTTON_MENU
, Tk_Offset(MenuButton
, menuName
), 0},
190 {TK_CONFIG_PIXELS
, "-padx", "padX", "Pad",
191 DEF_MENUBUTTON_PADX
, Tk_Offset(MenuButton
, padX
), 0},
192 {TK_CONFIG_PIXELS
, "-pady", "padY", "Pad",
193 DEF_MENUBUTTON_PADY
, Tk_Offset(MenuButton
, padY
), 0},
194 {TK_CONFIG_RELIEF
, "-relief", "relief", "Relief",
195 DEF_MENUBUTTON_RELIEF
, Tk_Offset(MenuButton
, relief
), 0},
196 {TK_CONFIG_UID
, "-state", "state", "State",
197 DEF_MENUBUTTON_STATE
, Tk_Offset(MenuButton
, state
), 0},
198 {TK_CONFIG_STRING
, "-text", "text", "Text",
199 DEF_MENUBUTTON_TEXT
, Tk_Offset(MenuButton
, text
), 0},
200 {TK_CONFIG_STRING
, "-textvariable", "textVariable", "Variable",
201 DEF_MENUBUTTON_TEXT_VARIABLE
, Tk_Offset(MenuButton
, textVarName
),
203 {TK_CONFIG_INT
, "-underline", "underline", "Underline",
204 DEF_MENUBUTTON_UNDERLINE
, Tk_Offset(MenuButton
, underline
), 0},
205 {TK_CONFIG_UID
, "-variable", "variable", "Variable",
206 DEF_MENUBUTTON_VARIABLE
, Tk_Offset(MenuButton
, varName
), 0},
207 {TK_CONFIG_INT
, "-width", "width", "Width",
208 DEF_MENUBUTTON_WIDTH
, Tk_Offset(MenuButton
, width
), 0},
209 {TK_CONFIG_END
, (char *) NULL
, (char *) NULL
, (char *) NULL
,
214 * Forward declarations for procedures defined later in this file:
217 static void ComputeMenuButtonGeometry
_ANSI_ARGS_((
219 static void MenuButtonEventProc
_ANSI_ARGS_((ClientData clientData
,
221 static char * MenuButtonTextVarProc
_ANSI_ARGS_((
222 ClientData clientData
, Tcl_Interp
*interp
,
223 char *name1
, char *name2
, int flags
));
224 static char * MenuButtonVarProc
_ANSI_ARGS_((ClientData clientData
,
225 Tcl_Interp
*interp
, char *name1
, char *name2
,
227 static int MenuButtonWidgetCmd
_ANSI_ARGS_((ClientData clientData
,
228 Tcl_Interp
*interp
, int argc
, char **argv
));
229 static int ConfigureMenuButton
_ANSI_ARGS_((Tcl_Interp
*interp
,
230 MenuButton
*mbPtr
, int argc
, char **argv
,
232 static void DestroyMenuButton
_ANSI_ARGS_((ClientData clientData
));
233 static void DisplayMenuButton
_ANSI_ARGS_((ClientData clientData
));
236 *--------------------------------------------------------------
238 * Tk_MenubuttonCmd --
240 * This procedure is invoked to process the "button", "label",
241 * "radiobutton", and "checkbutton" Tcl commands. See the
242 * user documentation for details on what it does.
245 * A standard Tcl result.
248 * See the user documentation.
250 *--------------------------------------------------------------
254 Tk_MenubuttonCmd(clientData
, interp
, argc
, argv
)
255 ClientData clientData
; /* Main window associated with
257 Tcl_Interp
*interp
; /* Current interpreter. */
258 int argc
; /* Number of arguments. */
259 char **argv
; /* Argument strings. */
261 register MenuButton
*mbPtr
;
262 Tk_Window tkwin
= (Tk_Window
) clientData
;
266 Tcl_AppendResult(interp
, "wrong # args: should be \"",
267 argv
[0], " pathName ?options?\"", (char *) NULL
);
272 * Create the new window.
275 new = Tk_CreateWindowFromPath(interp
, tkwin
, argv
[1], (char *) NULL
);
281 * Initialize the data structure for the button.
284 mbPtr
= (MenuButton
*) ckalloc(sizeof(MenuButton
));
286 mbPtr
->interp
= interp
;
287 mbPtr
->menuName
= NULL
;
288 mbPtr
->varName
= NULL
;
290 mbPtr
->underline
= -1;
291 mbPtr
->textVarName
= NULL
;
292 mbPtr
->bitmap
= None
;
293 mbPtr
->state
= tkNormalUid
;
294 mbPtr
->normalBorder
= NULL
;
295 mbPtr
->activeBorder
= NULL
;
296 mbPtr
->borderWidth
= 0;
297 mbPtr
->relief
= TK_RELIEF_FLAT
;
298 mbPtr
->fontPtr
= NULL
;
299 mbPtr
->normalFg
= NULL
;
300 mbPtr
->activeFg
= NULL
;
301 mbPtr
->disabledFg
= NULL
;
302 mbPtr
->normalTextGC
= NULL
;
303 mbPtr
->activeTextGC
= NULL
;
305 mbPtr
->disabledGC
= NULL
;
306 mbPtr
->cursor
= None
;
309 Tk_SetClass(mbPtr
->tkwin
, "Menubutton");
310 Tk_CreateEventHandler(mbPtr
->tkwin
, ExposureMask
|StructureNotifyMask
,
311 MenuButtonEventProc
, (ClientData
) mbPtr
);
312 Tcl_CreateCommand(interp
, Tk_PathName(mbPtr
->tkwin
), MenuButtonWidgetCmd
,
313 (ClientData
) mbPtr
, (void (*)()) NULL
);
314 if (ConfigureMenuButton(interp
, mbPtr
, argc
-2, argv
+2, 0) != TCL_OK
) {
315 Tk_DestroyWindow(mbPtr
->tkwin
);
319 interp
->result
= Tk_PathName(mbPtr
->tkwin
);
324 *--------------------------------------------------------------
326 * MenuButtonWidgetCmd --
328 * This procedure is invoked to process the Tcl command
329 * that corresponds to a widget managed by this module.
330 * See the user documentation for details on what it does.
333 * A standard Tcl result.
336 * See the user documentation.
338 *--------------------------------------------------------------
342 MenuButtonWidgetCmd(clientData
, interp
, argc
, argv
)
343 ClientData clientData
; /* Information about button widget. */
344 Tcl_Interp
*interp
; /* Current interpreter. */
345 int argc
; /* Number of arguments. */
346 char **argv
; /* Argument strings. */
348 register MenuButton
*mbPtr
= (MenuButton
*) clientData
;
354 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
355 " option ?arg arg ...?\"", (char *) NULL
);
358 Tk_Preserve((ClientData
) mbPtr
);
360 length
= strlen(argv
[1]);
361 if ((c
== 'a') && (strncmp(argv
[1], "activate", length
) == 0)) {
363 Tcl_AppendResult(interp
, "wrong # args: should be \"",
364 argv
[0], " activate\"", (char *) NULL
);
367 if (mbPtr
->state
!= tkDisabledUid
) {
368 mbPtr
->state
= tkActiveUid
;
369 Tk_SetBackgroundFromBorder(mbPtr
->tkwin
, mbPtr
->activeBorder
);
372 } else if ((c
== 'c') && (strncmp(argv
[1], "configure", length
) == 0)) {
374 result
= Tk_ConfigureInfo(interp
, mbPtr
->tkwin
, configSpecs
,
375 (char *) mbPtr
, (char *) NULL
, 0);
376 } else if (argc
== 3) {
377 result
= Tk_ConfigureInfo(interp
, mbPtr
->tkwin
, configSpecs
,
378 (char *) mbPtr
, argv
[2], 0);
380 result
= ConfigureMenuButton(interp
, mbPtr
, argc
-2, argv
+2,
381 TK_CONFIG_ARGV_ONLY
);
383 } else if ((c
== 'd') && (strncmp(argv
[1], "deactivate", length
) == 0)) {
385 Tcl_AppendResult(interp
, "wrong # args: should be \"",
386 argv
[0], " deactivate\"", (char *) NULL
);
389 if (mbPtr
->state
!= tkDisabledUid
) {
390 mbPtr
->state
= tkNormalUid
;
391 Tk_SetBackgroundFromBorder(mbPtr
->tkwin
, mbPtr
->normalBorder
);
394 } else if ((c
== 'p') && (strncmp(argv
[1], "post", length
) == 0)) {
396 Tcl_AppendResult(interp
, "wrong # args: should be \"",
397 argv
[0], " post\"", (char *) NULL
);
400 if ((mbPtr
->flags
& POSTED
) || (mbPtr
->menuName
== NULL
)
401 || (mbPtr
->state
== tkDisabledUid
)) {
406 * Store the name of the posted menu into the associated variable.
407 * This will cause any other menu posted via that variable to
408 * unpost itself and will cause this menu to post itself.
411 Tcl_SetVar(interp
, mbPtr
->varName
, Tk_PathName(mbPtr
->tkwin
),
413 } else if ((c
== 'u') && (strncmp(argv
[1], "unpost", length
) == 0)) {
415 Tcl_AppendResult(interp
, "wrong # args: should be \"",
416 argv
[0], " unpost\"", (char *) NULL
);
421 * The one-liner below looks simple, but it isn't. This code
422 * does the right thing even if this menu isn't posted anymore,
423 * but some other variable associated with the same variable
424 * is posted instead: it unposts whatever is posted. This
425 * approach is necessary because at present ButtonRelease
426 * events go to the menu button where the mouse button was
427 * first pressed; this may not be the same menu button that's
430 Tcl_SetVar(interp
, mbPtr
->varName
, "", TCL_GLOBAL_ONLY
);
432 Tcl_AppendResult(interp
, "bad option \"", argv
[1],
433 "\": must be activate, configure, deactivate, ",
434 "post, or unpost", (char *) NULL
);
438 Tk_Release((ClientData
) mbPtr
);
442 if (Tk_IsMapped(mbPtr
->tkwin
) && !(mbPtr
->flags
& REDRAW_PENDING
)) {
443 Tk_DoWhenIdle(DisplayMenuButton
, (ClientData
) mbPtr
);
444 mbPtr
->flags
|= REDRAW_PENDING
;
449 Tk_Release((ClientData
) mbPtr
);
454 *----------------------------------------------------------------------
456 * DestroyMenuButton --
458 * This procedure is invoked to recycle all of the resources
459 * associated with a button widget. It is invoked as a
460 * when-idle handler in order to make sure that there is no
461 * other use of the button pending at the time of the deletion.
467 * Everything associated with the widget is freed up.
469 *----------------------------------------------------------------------
473 DestroyMenuButton(clientData
)
474 ClientData clientData
; /* Info about button widget. */
476 register MenuButton
*mbPtr
= (MenuButton
*) clientData
;
477 if (mbPtr
->menuName
!= NULL
) {
478 ckfree(mbPtr
->menuName
);
480 if (mbPtr
->varName
!= NULL
) {
481 Tcl_UntraceVar(mbPtr
->interp
, mbPtr
->varName
,
482 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
483 MenuButtonVarProc
, (ClientData
) mbPtr
);
485 if (mbPtr
->text
!= NULL
) {
488 if (mbPtr
->textVarName
!= NULL
) {
489 Tcl_UntraceVar(mbPtr
->interp
, mbPtr
->textVarName
,
490 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
491 MenuButtonTextVarProc
, (ClientData
) mbPtr
);
492 ckfree(mbPtr
->textVarName
);
494 if (mbPtr
->bitmap
!= None
) {
495 #if defined(USE_XPM3)
496 Tk_FreePixmap(mbPtr
->bitmap
);
498 Tk_FreeBitmap(mbPtr
->bitmap
);
501 if (mbPtr
->normalBorder
!= NULL
) {
502 Tk_Free3DBorder(mbPtr
->normalBorder
);
504 if (mbPtr
->activeBorder
!= NULL
) {
505 Tk_Free3DBorder(mbPtr
->activeBorder
);
507 if (mbPtr
->fontPtr
!= NULL
) {
508 Tk_FreeFontStruct(mbPtr
->fontPtr
);
510 if (mbPtr
->normalFg
!= NULL
) {
511 Tk_FreeColor(mbPtr
->normalFg
);
513 if (mbPtr
->activeFg
!= NULL
) {
514 Tk_FreeColor(mbPtr
->activeFg
);
516 if (mbPtr
->disabledFg
!= NULL
) {
517 Tk_FreeColor(mbPtr
->disabledFg
);
519 if (mbPtr
->normalTextGC
!= None
) {
520 Tk_FreeGC(mbPtr
->normalTextGC
);
522 if (mbPtr
->activeTextGC
!= None
) {
523 Tk_FreeGC(mbPtr
->activeTextGC
);
525 if (mbPtr
->gray
!= None
) {
526 Tk_FreeBitmap(mbPtr
->gray
);
528 if (mbPtr
->disabledGC
!= None
) {
529 Tk_FreeGC(mbPtr
->disabledGC
);
531 if (mbPtr
->cursor
!= None
) {
532 Tk_FreeCursor(mbPtr
->cursor
);
534 ckfree((char *) mbPtr
);
538 *----------------------------------------------------------------------
540 * ConfigureMenuButton --
542 * This procedure is called to process an argv/argc list, plus
543 * the Tk option database, in order to configure (or
544 * reconfigure) a menubutton widget.
547 * The return value is a standard Tcl result. If TCL_ERROR is
548 * returned, then interp->result contains an error message.
551 * Configuration information, such as text string, colors, font,
552 * etc. get set for mbPtr; old resources get freed, if there
553 * were any. The menubutton is redisplayed.
555 *----------------------------------------------------------------------
559 ConfigureMenuButton(interp
, mbPtr
, argc
, argv
, flags
)
560 Tcl_Interp
*interp
; /* Used for error reporting. */
561 register MenuButton
*mbPtr
; /* Information about widget; may or may
562 * not already have values for some fields. */
563 int argc
; /* Number of valid entries in argv. */
564 char **argv
; /* Arguments. */
565 int flags
; /* Flags to pass to Tk_ConfigureWidget. */
575 * Eliminate any existing traces on variables monitored by the button.
578 if (mbPtr
->varName
!= NULL
) {
579 Tcl_UntraceVar(interp
, mbPtr
->varName
,
580 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
581 MenuButtonVarProc
, (ClientData
) mbPtr
);
583 if (mbPtr
->textVarName
!= NULL
) {
584 Tcl_UntraceVar(interp
, mbPtr
->textVarName
,
585 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
586 MenuButtonTextVarProc
, (ClientData
) mbPtr
);
589 oldGroup
= mbPtr
->varName
;
590 result
= Tk_ConfigureWidget(interp
, mbPtr
->tkwin
, configSpecs
,
591 argc
, argv
, (char *) mbPtr
, flags
);
592 if (oldGroup
!= mbPtr
->varName
) {
593 Tk_UnshareEvents(mbPtr
->tkwin
, oldGroup
);
594 Tk_ShareEvents(mbPtr
->tkwin
, mbPtr
->varName
);
596 if (result
!= TCL_OK
) {
601 * A few options need special processing, such as setting the
602 * background from a 3-D border, or filling in complicated
603 * defaults that couldn't be specified to Tk_ConfigureWidget.
606 if (mbPtr
->state
== tkActiveUid
) {
607 Tk_SetBackgroundFromBorder(mbPtr
->tkwin
, mbPtr
->activeBorder
);
609 Tk_SetBackgroundFromBorder(mbPtr
->tkwin
, mbPtr
->normalBorder
);
610 if ((mbPtr
->state
!= tkNormalUid
) && (mbPtr
->state
!= tkDisabledUid
)) {
611 Tcl_AppendResult(interp
, "bad state value \"", mbPtr
->state
,
612 "\": must be normal, active, or disabled", (char *) NULL
);
613 mbPtr
->state
= tkNormalUid
;
618 gcValues
.font
= mbPtr
->fontPtr
->fid
;
619 gcValues
.foreground
= mbPtr
->normalFg
->pixel
;
620 gcValues
.background
= Tk_3DBorderColor(mbPtr
->normalBorder
)->pixel
;
623 * Note: GraphicsExpose events are disabled in GC's because they're
624 * used to copy stuff from an off-screen pixmap onto the screen (we know
625 * that there's no problem with obscured areas).
628 gcValues
.graphics_exposures
= False
;
629 newGC
= Tk_GetGC(mbPtr
->tkwin
,
630 GCForeground
|GCBackground
|GCFont
|GCGraphicsExposures
, &gcValues
);
631 if (mbPtr
->normalTextGC
!= None
) {
632 Tk_FreeGC(mbPtr
->normalTextGC
);
634 mbPtr
->normalTextGC
= newGC
;
636 gcValues
.font
= mbPtr
->fontPtr
->fid
;
637 gcValues
.foreground
= mbPtr
->activeFg
->pixel
;
638 gcValues
.background
= Tk_3DBorderColor(mbPtr
->activeBorder
)->pixel
;
639 newGC
= Tk_GetGC(mbPtr
->tkwin
, GCForeground
|GCBackground
|GCFont
,
641 if (mbPtr
->activeTextGC
!= None
) {
642 Tk_FreeGC(mbPtr
->activeTextGC
);
644 mbPtr
->activeTextGC
= newGC
;
646 gcValues
.font
= mbPtr
->fontPtr
->fid
;
647 gcValues
.background
= Tk_3DBorderColor(mbPtr
->normalBorder
)->pixel
;
648 if (mbPtr
->disabledFg
!= NULL
) {
649 gcValues
.foreground
= mbPtr
->disabledFg
->pixel
;
650 mask
= GCForeground
|GCBackground
|GCFont
;
652 gcValues
.foreground
= gcValues
.background
;
653 if (mbPtr
->gray
== None
) {
654 mbPtr
->gray
= Tk_GetBitmap(interp
, mbPtr
->tkwin
,
655 Tk_GetUid("gray50"));
656 if (mbPtr
->gray
== None
) {
660 gcValues
.fill_style
= FillStippled
;
661 gcValues
.stipple
= mbPtr
->gray
;
662 mask
= GCForeground
|GCFillStyle
|GCStipple
;
664 newGC
= Tk_GetGC(mbPtr
->tkwin
, mask
, &gcValues
);
665 if (mbPtr
->disabledGC
!= None
) {
666 Tk_FreeGC(mbPtr
->disabledGC
);
668 mbPtr
->disabledGC
= newGC
;
670 if (mbPtr
->padX
< 0) {
673 if (mbPtr
->padY
< 0) {
678 * Set up a trace on the menu button's variable, then initialize
679 * the variable if it doesn't already exist, so that it can be
680 * accessed immediately from Tcl code without fear of
681 * "nonexistent variable" errors.
684 value
= Tcl_GetVar(interp
, mbPtr
->varName
, TCL_GLOBAL_ONLY
);
686 Tcl_SetVar(interp
, mbPtr
->varName
, "", TCL_GLOBAL_ONLY
);
688 Tcl_TraceVar(interp
, mbPtr
->varName
,
689 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
690 MenuButtonVarProc
, (ClientData
) mbPtr
);
693 * Set up a trace on the variable that determines what's displayed
694 * in the menu button, if such a trace has been requested.
697 if ((mbPtr
->bitmap
== None
) && (mbPtr
->textVarName
!= NULL
)) {
700 value
= Tcl_GetVar(interp
, mbPtr
->textVarName
, TCL_GLOBAL_ONLY
);
702 Tcl_SetVar(interp
, mbPtr
->textVarName
, mbPtr
->text
,
705 if (mbPtr
->text
!= NULL
) {
708 mbPtr
->text
= ckalloc((unsigned) (strlen(value
) + 1));
709 strcpy(mbPtr
->text
, value
);
711 Tcl_TraceVar(interp
, mbPtr
->textVarName
,
712 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
713 MenuButtonTextVarProc
, (ClientData
) mbPtr
);
717 * Recompute the geometry for the button.
720 ComputeMenuButtonGeometry(mbPtr
);
723 * Lastly, arrange for the button to be redisplayed.
726 if (Tk_IsMapped(mbPtr
->tkwin
) && !(mbPtr
->flags
& REDRAW_PENDING
)) {
727 Tk_DoWhenIdle(DisplayMenuButton
, (ClientData
) mbPtr
);
728 mbPtr
->flags
|= REDRAW_PENDING
;
735 *----------------------------------------------------------------------
737 * DisplayMenuButton --
739 * This procedure is invoked to display a menubutton widget.
745 * Commands are output to X to display the menubutton in its
748 *----------------------------------------------------------------------
752 DisplayMenuButton(clientData
)
753 ClientData clientData
; /* Information about widget. */
755 register MenuButton
*mbPtr
= (MenuButton
*) clientData
;
759 int x
= 0; /* Initialization needed only to stop
760 * compiler warning. */
762 register Tk_Window tkwin
= mbPtr
->tkwin
;
764 mbPtr
->flags
&= ~REDRAW_PENDING
;
765 if ((mbPtr
->tkwin
== NULL
) || !Tk_IsMapped(tkwin
)) {
769 if ((mbPtr
->state
== tkDisabledUid
) && (mbPtr
->disabledFg
!= NULL
)) {
770 gc
= mbPtr
->disabledGC
;
771 border
= mbPtr
->normalBorder
;
772 } else if (mbPtr
->state
== tkActiveUid
) {
773 gc
= mbPtr
->activeTextGC
;
774 border
= mbPtr
->activeBorder
;
776 gc
= mbPtr
->normalTextGC
;
777 border
= mbPtr
->normalBorder
;
781 * In order to avoid screen flashes, this procedure redraws
782 * the menu button in a pixmap, then copies the pixmap to the
783 * screen in a single operation. This means that there's no
784 * point in time where the on-sreen image has been cleared.
787 pixmap
= XCreatePixmap(Tk_Display(tkwin
), Tk_WindowId(tkwin
),
788 Tk_Width(tkwin
), Tk_Height(tkwin
),
789 Tk_DefaultDepth(Tk_Screen(tkwin
)));
790 Tk_Fill3DRectangle(Tk_Display(tkwin
), pixmap
, border
,
791 0, 0, Tk_Width(tkwin
), Tk_Height(tkwin
), 0, TK_RELIEF_FLAT
);
794 * Display bitmap or text for button.
797 if (mbPtr
->bitmap
!= None
) {
798 unsigned int width
, height
;
800 #if defined(USE_XPM3)
801 Tk_SizeOfPixmap(mbPtr
->bitmap
, &width
, &height
);
803 Tk_SizeOfBitmap(mbPtr
->bitmap
, &width
, &height
);
805 switch (mbPtr
->anchor
) {
806 case TK_ANCHOR_NW
: case TK_ANCHOR_W
: case TK_ANCHOR_SW
:
807 x
+= mbPtr
->borderWidth
+ mbPtr
->padX
;
809 case TK_ANCHOR_N
: case TK_ANCHOR_CENTER
: case TK_ANCHOR_S
:
810 x
+= (Tk_Width(tkwin
) - width
)/2;
813 x
+= Tk_Width(tkwin
) - mbPtr
->borderWidth
- mbPtr
->padX
817 switch (mbPtr
->anchor
) {
818 case TK_ANCHOR_NW
: case TK_ANCHOR_N
: case TK_ANCHOR_NE
:
819 y
= mbPtr
->borderWidth
+ mbPtr
->padY
;
821 case TK_ANCHOR_W
: case TK_ANCHOR_CENTER
: case TK_ANCHOR_E
:
822 y
= (Tk_Height(tkwin
) - height
)/2;
825 y
= Tk_Height(tkwin
) - mbPtr
->borderWidth
- mbPtr
->padY
829 #if defined(USE_XPM3)
830 XCopyArea(Tk_Display(tkwin
), mbPtr
->bitmap
, pixmap
,
831 gc
, 0, 0, width
, height
, x
, y
);
833 XCopyPlane(Tk_Display(tkwin
), mbPtr
->bitmap
, pixmap
,
834 gc
, 0, 0, width
, height
, x
, y
, 1);
837 switch (mbPtr
->anchor
) {
838 case TK_ANCHOR_NW
: case TK_ANCHOR_W
: case TK_ANCHOR_SW
:
839 x
= mbPtr
->borderWidth
+ mbPtr
->padX
+ mbPtr
->leftBearing
;
841 case TK_ANCHOR_N
: case TK_ANCHOR_CENTER
: case TK_ANCHOR_S
:
842 x
= (Tk_Width(tkwin
) + mbPtr
->leftBearing
843 - mbPtr
->rightBearing
)/2;
846 x
= Tk_Width(tkwin
) - mbPtr
->borderWidth
- mbPtr
->padX
847 - mbPtr
->rightBearing
;
850 switch (mbPtr
->anchor
) {
851 case TK_ANCHOR_NW
: case TK_ANCHOR_N
: case TK_ANCHOR_NE
:
852 y
= mbPtr
->borderWidth
+ mbPtr
->fontPtr
->ascent
855 case TK_ANCHOR_W
: case TK_ANCHOR_CENTER
: case TK_ANCHOR_E
:
856 y
= (Tk_Height(tkwin
) + mbPtr
->fontPtr
->ascent
857 - mbPtr
->fontPtr
->descent
)/2;
860 y
= Tk_Height(tkwin
) - mbPtr
->borderWidth
- mbPtr
->padY
861 - mbPtr
->fontPtr
->descent
;
864 XDrawString(Tk_Display(tkwin
), pixmap
, gc
, x
, y
, mbPtr
->text
,
866 if (mbPtr
->underline
>= 0) {
867 TkUnderlineChars(Tk_Display(tkwin
), pixmap
, gc
, mbPtr
->fontPtr
,
868 mbPtr
->text
, x
, y
, TK_NEWLINES_NOT_SPECIAL
,
869 mbPtr
->underline
, mbPtr
->underline
);
874 * If the menu button is disabled with a stipple rather than a special
875 * foreground color, generate the stippled effect.
878 if ((mbPtr
->state
== tkDisabledUid
) && (mbPtr
->disabledFg
== NULL
)) {
879 XFillRectangle(Tk_Display(tkwin
), pixmap
, mbPtr
->disabledGC
,
880 mbPtr
->borderWidth
, mbPtr
->borderWidth
,
881 (unsigned) (Tk_Width(tkwin
) - 2*mbPtr
->borderWidth
),
882 (unsigned) (Tk_Height(tkwin
) - 2*mbPtr
->borderWidth
));
886 * Draw the border last. This way, if the menu button's contents
887 * overflow onto the border they'll be covered up by the border.
890 if (mbPtr
->relief
!= TK_RELIEF_FLAT
) {
891 Tk_Draw3DRectangle(Tk_Display(tkwin
), pixmap
, border
,
892 0, 0, Tk_Width(tkwin
), Tk_Height(tkwin
),
893 mbPtr
->borderWidth
, mbPtr
->relief
);
897 * Copy the information from the off-screen pixmap onto the screen,
898 * then delete the pixmap.
901 XCopyArea(Tk_Display(tkwin
), pixmap
, Tk_WindowId(tkwin
),
902 mbPtr
->normalTextGC
, 0, 0, Tk_Width(tkwin
), Tk_Height(tkwin
), 0, 0);
903 XFreePixmap(Tk_Display(tkwin
), pixmap
);
907 *--------------------------------------------------------------
909 * MenuButtonEventProc --
911 * This procedure is invoked by the Tk dispatcher for various
918 * When the window gets deleted, internal structures get
919 * cleaned up. When it gets exposed, it is redisplayed.
921 *--------------------------------------------------------------
925 MenuButtonEventProc(clientData
, eventPtr
)
926 ClientData clientData
; /* Information about window. */
927 XEvent
*eventPtr
; /* Information about event. */
929 MenuButton
*mbPtr
= (MenuButton
*) clientData
;
930 if ((eventPtr
->type
== Expose
) && (eventPtr
->xexpose
.count
== 0)) {
931 if ((mbPtr
->tkwin
!= NULL
) && !(mbPtr
->flags
& REDRAW_PENDING
)) {
932 Tk_DoWhenIdle(DisplayMenuButton
, (ClientData
) mbPtr
);
933 mbPtr
->flags
|= REDRAW_PENDING
;
935 } else if (eventPtr
->type
== DestroyNotify
) {
936 Tcl_DeleteCommand(mbPtr
->interp
, Tk_PathName(mbPtr
->tkwin
));
939 * Careful! Must delete the event-sharing information here
940 * rather than in DestroyMenuButton. By the time that procedure
941 * is called the tkwin may have been reused, resulting in some
942 * other window accidentally being cut off from shared events.
945 Tk_UnshareEvents(mbPtr
->tkwin
, mbPtr
->varName
);
947 if (mbPtr
->flags
& REDRAW_PENDING
) {
948 Tk_CancelIdleCall(DisplayMenuButton
, (ClientData
) mbPtr
);
950 Tk_EventuallyFree((ClientData
) mbPtr
, DestroyMenuButton
);
955 *----------------------------------------------------------------------
957 * ComputeMenuButtonGeometry --
959 * After changes in a menu button's text or bitmap, this procedure
960 * recomputes the menu button's geometry and passes this information
961 * along to the geometry manager for the window.
967 * The menu button's window may change size.
969 *----------------------------------------------------------------------
973 ComputeMenuButtonGeometry(mbPtr
)
974 register MenuButton
*mbPtr
; /* Widget record for menu button. */
978 unsigned int width
, height
;
980 if (mbPtr
->bitmap
!= None
) {
981 #if defined(USE_XPM3)
982 Tk_SizeOfPixmap(mbPtr
->bitmap
, &width
, &height
);
984 Tk_SizeOfBitmap(mbPtr
->bitmap
, &width
, &height
);
986 if (mbPtr
->width
> 0) {
987 width
= mbPtr
->width
;
989 if (mbPtr
->height
> 0) {
990 height
= mbPtr
->height
;
993 mbPtr
->textLength
= strlen(mbPtr
->text
);
994 XTextExtents(mbPtr
->fontPtr
, mbPtr
->text
, mbPtr
->textLength
,
995 &dummy
, &dummy
, &dummy
, &bbox
);
996 mbPtr
->leftBearing
= bbox
.lbearing
;
997 mbPtr
->rightBearing
= bbox
.rbearing
;
998 width
= bbox
.lbearing
+ bbox
.rbearing
;
999 height
= mbPtr
->fontPtr
->ascent
+ mbPtr
->fontPtr
->descent
;
1000 if (mbPtr
->width
> 0) {
1001 width
= mbPtr
->width
* XTextWidth(mbPtr
->fontPtr
, "0", 1);
1003 if (mbPtr
->height
> 0) {
1004 height
*= mbPtr
->height
;
1008 width
+= 2*mbPtr
->padX
;
1009 height
+= 2*mbPtr
->padY
;
1010 Tk_GeometryRequest(mbPtr
->tkwin
, (int) (width
+ 2*mbPtr
->borderWidth
),
1011 (int) (height
+ 2*mbPtr
->borderWidth
));
1012 Tk_SetInternalBorder(mbPtr
->tkwin
, mbPtr
->borderWidth
);
1016 *--------------------------------------------------------------
1018 * MenuButtonVarProc --
1020 * This procedure is invoked when someone changes the
1021 * state variable associated with a menubutton. This causes
1022 * the posted/unposted state of the menu to change if needed
1023 * to match the variable's new value.
1026 * NULL is always returned.
1029 * The menu may be posted or unposted.
1031 *--------------------------------------------------------------
1036 MenuButtonVarProc(clientData
, interp
, name1
, name2
, flags
)
1037 ClientData clientData
; /* Information about button. */
1038 Tcl_Interp
*interp
; /* Interpreter containing variable. */
1039 char *name1
; /* First part of variable's name. */
1040 char *name2
; /* Second part of variable's name. */
1041 int flags
; /* Describes what's happening to variable. */
1043 register MenuButton
*mbPtr
= (MenuButton
*) clientData
;
1048 * If the variable is being unset, then just re-establish the
1049 * trace unless the whole interpreter is going away. Also unpost
1053 newFlags
= mbPtr
->flags
;
1054 if (flags
& TCL_TRACE_UNSETS
) {
1055 newFlags
&= ~POSTED
;
1056 if ((flags
& TCL_TRACE_DESTROYED
) && !(flags
& TCL_INTERP_DESTROYED
)) {
1057 Tcl_TraceVar2(interp
, name1
, name2
,
1058 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
1059 MenuButtonVarProc
, clientData
);
1064 * Use the value of the variable to update the posted status of
1068 value
= Tcl_GetVar2(interp
, name1
, name2
, flags
& TCL_GLOBAL_ONLY
);
1069 if (strcmp(value
, Tk_PathName(mbPtr
->tkwin
)) == 0) {
1072 newFlags
&= ~POSTED
;
1076 if ((mbPtr
->menuName
!= NULL
) && (newFlags
!= mbPtr
->flags
)) {
1077 mbPtr
->flags
= newFlags
;
1078 if (newFlags
& POSTED
) {
1083 * Post the menu just below the menu button.
1086 Tk_GetRootCoords(mbPtr
->tkwin
, &x
, &y
);
1087 y
+= Tk_Height(mbPtr
->tkwin
);
1088 sprintf(string
, "%d %d ", x
, y
);
1089 if (Tcl_VarEval(interp
, mbPtr
->menuName
, " post ", string
,
1090 mbPtr
->varName
, (char *) NULL
) != TCL_OK
) {
1091 TkBindError(interp
);
1094 if (Tcl_VarEval(interp
, mbPtr
->menuName
, " unpost",
1095 (char *) NULL
) != TCL_OK
) {
1096 TkBindError(interp
);
1100 return (char *) NULL
;
1104 *--------------------------------------------------------------
1106 * MenuButtonTextVarProc --
1108 * This procedure is invoked when someone changes the variable
1109 * whose contents are to be displayed in a menu button.
1112 * NULL is always returned.
1115 * The text displayed in the menu button will change to match the
1118 *--------------------------------------------------------------
1123 MenuButtonTextVarProc(clientData
, interp
, name1
, name2
, flags
)
1124 ClientData clientData
; /* Information about button. */
1125 Tcl_Interp
*interp
; /* Interpreter containing variable. */
1126 char *name1
; /* Name of variable. */
1127 char *name2
; /* Second part of variable name. */
1128 int flags
; /* Information about what happened. */
1130 register MenuButton
*mbPtr
= (MenuButton
*) clientData
;
1134 * If the variable is unset, then immediately recreate it unless
1135 * the whole interpreter is going away.
1138 if (flags
& TCL_TRACE_UNSETS
) {
1139 if ((flags
& TCL_TRACE_DESTROYED
) && !(flags
& TCL_INTERP_DESTROYED
)) {
1140 Tcl_SetVar2(interp
, name1
, name2
, mbPtr
->text
,
1141 flags
& TCL_GLOBAL_ONLY
);
1142 Tcl_TraceVar2(interp
, name1
, name2
,
1143 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
1144 MenuButtonTextVarProc
, clientData
);
1146 return (char *) NULL
;
1149 value
= Tcl_GetVar2(interp
, name1
, name2
, flags
& TCL_GLOBAL_ONLY
);
1150 if (value
== NULL
) {
1153 if (mbPtr
->text
!= NULL
) {
1154 ckfree(mbPtr
->text
);
1156 mbPtr
->text
= ckalloc((unsigned) (strlen(value
) + 1));
1157 strcpy(mbPtr
->text
, value
);
1158 ComputeMenuButtonGeometry(mbPtr
);
1160 if ((mbPtr
->tkwin
!= NULL
) && Tk_IsMapped(mbPtr
->tkwin
)
1161 && !(mbPtr
->flags
& REDRAW_PENDING
)) {
1162 Tk_DoWhenIdle(DisplayMenuButton
, (ClientData
) mbPtr
);
1163 mbPtr
->flags
|= REDRAW_PENDING
;
1165 return (char *) NULL
;