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 *--------------------------------------------------------------
255 ClientData clientData
, /* Main window associated with
257 Tcl_Interp
*interp
, /* Current interpreter. */
258 int argc
, /* Number of arguments. */
259 char **argv
/* Argument strings. */
262 register MenuButton
*mbPtr
;
263 Tk_Window tkwin
= (Tk_Window
) clientData
;
267 Tcl_AppendResult(interp
, "wrong # args: should be \"",
268 argv
[0], " pathName ?options?\"", (char *) NULL
);
273 * Create the new window.
276 new = Tk_CreateWindowFromPath(interp
, tkwin
, argv
[1], (char *) NULL
);
282 * Initialize the data structure for the button.
285 mbPtr
= (MenuButton
*) ckalloc(sizeof(MenuButton
));
287 mbPtr
->interp
= interp
;
288 mbPtr
->menuName
= NULL
;
289 mbPtr
->varName
= NULL
;
291 mbPtr
->underline
= -1;
292 mbPtr
->textVarName
= NULL
;
293 mbPtr
->bitmap
= None
;
294 mbPtr
->state
= tkNormalUid
;
295 mbPtr
->normalBorder
= NULL
;
296 mbPtr
->activeBorder
= NULL
;
297 mbPtr
->borderWidth
= 0;
298 mbPtr
->relief
= TK_RELIEF_FLAT
;
299 mbPtr
->fontPtr
= NULL
;
300 mbPtr
->normalFg
= NULL
;
301 mbPtr
->activeFg
= NULL
;
302 mbPtr
->disabledFg
= NULL
;
303 mbPtr
->normalTextGC
= NULL
;
304 mbPtr
->activeTextGC
= NULL
;
306 mbPtr
->disabledGC
= NULL
;
307 mbPtr
->cursor
= None
;
310 Tk_SetClass(mbPtr
->tkwin
, "Menubutton");
311 Tk_CreateEventHandler(mbPtr
->tkwin
, ExposureMask
|StructureNotifyMask
,
312 MenuButtonEventProc
, (ClientData
) mbPtr
);
313 Tcl_CreateCommand(interp
, Tk_PathName(mbPtr
->tkwin
), MenuButtonWidgetCmd
,
314 (ClientData
) mbPtr
, (void (*)(int *)) NULL
);
315 if (ConfigureMenuButton(interp
, mbPtr
, argc
-2, argv
+2, 0) != TCL_OK
) {
316 Tk_DestroyWindow(mbPtr
->tkwin
);
320 interp
->result
= Tk_PathName(mbPtr
->tkwin
);
325 *--------------------------------------------------------------
327 * MenuButtonWidgetCmd --
329 * This procedure is invoked to process the Tcl command
330 * that corresponds to a widget managed by this module.
331 * See the user documentation for details on what it does.
334 * A standard Tcl result.
337 * See the user documentation.
339 *--------------------------------------------------------------
343 MenuButtonWidgetCmd (
344 ClientData clientData
, /* Information about button widget. */
345 Tcl_Interp
*interp
, /* Current interpreter. */
346 int argc
, /* Number of arguments. */
347 char **argv
/* Argument strings. */
350 register MenuButton
*mbPtr
= (MenuButton
*) clientData
;
356 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
357 " option ?arg arg ...?\"", (char *) NULL
);
360 Tk_Preserve((ClientData
) mbPtr
);
362 length
= strlen(argv
[1]);
363 if ((c
== 'a') && (strncmp(argv
[1], "activate", length
) == 0)) {
365 Tcl_AppendResult(interp
, "wrong # args: should be \"",
366 argv
[0], " activate\"", (char *) NULL
);
369 if (mbPtr
->state
!= tkDisabledUid
) {
370 mbPtr
->state
= tkActiveUid
;
371 Tk_SetBackgroundFromBorder(mbPtr
->tkwin
, mbPtr
->activeBorder
);
374 } else if ((c
== 'c') && (strncmp(argv
[1], "configure", length
) == 0)) {
376 result
= Tk_ConfigureInfo(interp
, mbPtr
->tkwin
, configSpecs
,
377 (char *) mbPtr
, (char *) NULL
, 0);
378 } else if (argc
== 3) {
379 result
= Tk_ConfigureInfo(interp
, mbPtr
->tkwin
, configSpecs
,
380 (char *) mbPtr
, argv
[2], 0);
382 result
= ConfigureMenuButton(interp
, mbPtr
, argc
-2, argv
+2,
383 TK_CONFIG_ARGV_ONLY
);
385 } else if ((c
== 'd') && (strncmp(argv
[1], "deactivate", length
) == 0)) {
387 Tcl_AppendResult(interp
, "wrong # args: should be \"",
388 argv
[0], " deactivate\"", (char *) NULL
);
391 if (mbPtr
->state
!= tkDisabledUid
) {
392 mbPtr
->state
= tkNormalUid
;
393 Tk_SetBackgroundFromBorder(mbPtr
->tkwin
, mbPtr
->normalBorder
);
396 } else if ((c
== 'p') && (strncmp(argv
[1], "post", length
) == 0)) {
398 Tcl_AppendResult(interp
, "wrong # args: should be \"",
399 argv
[0], " post\"", (char *) NULL
);
402 if ((mbPtr
->flags
& POSTED
) || (mbPtr
->menuName
== NULL
)
403 || (mbPtr
->state
== tkDisabledUid
)) {
408 * Store the name of the posted menu into the associated variable.
409 * This will cause any other menu posted via that variable to
410 * unpost itself and will cause this menu to post itself.
413 Tcl_SetVar(interp
, mbPtr
->varName
, Tk_PathName(mbPtr
->tkwin
),
415 } else if ((c
== 'u') && (strncmp(argv
[1], "unpost", length
) == 0)) {
417 Tcl_AppendResult(interp
, "wrong # args: should be \"",
418 argv
[0], " unpost\"", (char *) NULL
);
423 * The one-liner below looks simple, but it isn't. This code
424 * does the right thing even if this menu isn't posted anymore,
425 * but some other variable associated with the same variable
426 * is posted instead: it unposts whatever is posted. This
427 * approach is necessary because at present ButtonRelease
428 * events go to the menu button where the mouse button was
429 * first pressed; this may not be the same menu button that's
432 Tcl_SetVar(interp
, mbPtr
->varName
, "", TCL_GLOBAL_ONLY
);
434 Tcl_AppendResult(interp
, "bad option \"", argv
[1],
435 "\": must be activate, configure, deactivate, ",
436 "post, or unpost", (char *) NULL
);
440 Tk_Release((ClientData
) mbPtr
);
444 if (Tk_IsMapped(mbPtr
->tkwin
) && !(mbPtr
->flags
& REDRAW_PENDING
)) {
445 Tk_DoWhenIdle(DisplayMenuButton
, (ClientData
) mbPtr
);
446 mbPtr
->flags
|= REDRAW_PENDING
;
451 Tk_Release((ClientData
) mbPtr
);
456 *----------------------------------------------------------------------
458 * DestroyMenuButton --
460 * This procedure is invoked to recycle all of the resources
461 * associated with a button widget. It is invoked as a
462 * when-idle handler in order to make sure that there is no
463 * other use of the button pending at the time of the deletion.
469 * Everything associated with the widget is freed up.
471 *----------------------------------------------------------------------
476 ClientData clientData
/* Info about button widget. */
479 register MenuButton
*mbPtr
= (MenuButton
*) clientData
;
480 if (mbPtr
->menuName
!= NULL
) {
481 ckfree(mbPtr
->menuName
);
483 if (mbPtr
->varName
!= NULL
) {
484 Tcl_UntraceVar(mbPtr
->interp
, mbPtr
->varName
,
485 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
486 MenuButtonVarProc
, (ClientData
) mbPtr
);
488 if (mbPtr
->text
!= NULL
) {
491 if (mbPtr
->textVarName
!= NULL
) {
492 Tcl_UntraceVar(mbPtr
->interp
, mbPtr
->textVarName
,
493 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
494 MenuButtonTextVarProc
, (ClientData
) mbPtr
);
495 ckfree(mbPtr
->textVarName
);
497 if (mbPtr
->bitmap
!= None
) {
498 #if defined(USE_XPM3)
499 Tk_FreePixmap(mbPtr
->bitmap
);
501 Tk_FreeBitmap(mbPtr
->bitmap
);
504 if (mbPtr
->normalBorder
!= NULL
) {
505 Tk_Free3DBorder(mbPtr
->normalBorder
);
507 if (mbPtr
->activeBorder
!= NULL
) {
508 Tk_Free3DBorder(mbPtr
->activeBorder
);
510 if (mbPtr
->fontPtr
!= NULL
) {
511 Tk_FreeFontStruct(mbPtr
->fontPtr
);
513 if (mbPtr
->normalFg
!= NULL
) {
514 Tk_FreeColor(mbPtr
->normalFg
);
516 if (mbPtr
->activeFg
!= NULL
) {
517 Tk_FreeColor(mbPtr
->activeFg
);
519 if (mbPtr
->disabledFg
!= NULL
) {
520 Tk_FreeColor(mbPtr
->disabledFg
);
522 if (mbPtr
->normalTextGC
!= None
) {
523 Tk_FreeGC(mbPtr
->normalTextGC
);
525 if (mbPtr
->activeTextGC
!= None
) {
526 Tk_FreeGC(mbPtr
->activeTextGC
);
528 if (mbPtr
->gray
!= None
) {
529 Tk_FreeBitmap(mbPtr
->gray
);
531 if (mbPtr
->disabledGC
!= None
) {
532 Tk_FreeGC(mbPtr
->disabledGC
);
534 if (mbPtr
->cursor
!= None
) {
535 Tk_FreeCursor(mbPtr
->cursor
);
537 ckfree((char *) mbPtr
);
541 *----------------------------------------------------------------------
543 * ConfigureMenuButton --
545 * This procedure is called to process an argv/argc list, plus
546 * the Tk option database, in order to configure (or
547 * reconfigure) a menubutton widget.
550 * The return value is a standard Tcl result. If TCL_ERROR is
551 * returned, then interp->result contains an error message.
554 * Configuration information, such as text string, colors, font,
555 * etc. get set for mbPtr; old resources get freed, if there
556 * were any. The menubutton is redisplayed.
558 *----------------------------------------------------------------------
562 ConfigureMenuButton (
563 Tcl_Interp
*interp
, /* Used for error reporting. */
564 register MenuButton
*mbPtr
, /* Information about widget; may or may
565 * not already have values for some fields. */
566 int argc
, /* Number of valid entries in argv. */
567 char **argv
, /* Arguments. */
568 int flags
/* Flags to pass to Tk_ConfigureWidget. */
579 * Eliminate any existing traces on variables monitored by the button.
582 if (mbPtr
->varName
!= NULL
) {
583 Tcl_UntraceVar(interp
, mbPtr
->varName
,
584 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
585 MenuButtonVarProc
, (ClientData
) mbPtr
);
587 if (mbPtr
->textVarName
!= NULL
) {
588 Tcl_UntraceVar(interp
, mbPtr
->textVarName
,
589 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
590 MenuButtonTextVarProc
, (ClientData
) mbPtr
);
593 oldGroup
= mbPtr
->varName
;
594 result
= Tk_ConfigureWidget(interp
, mbPtr
->tkwin
, configSpecs
,
595 argc
, argv
, (char *) mbPtr
, flags
);
596 if (oldGroup
!= mbPtr
->varName
) {
597 Tk_UnshareEvents(mbPtr
->tkwin
, oldGroup
);
598 Tk_ShareEvents(mbPtr
->tkwin
, mbPtr
->varName
);
600 if (result
!= TCL_OK
) {
605 * A few options need special processing, such as setting the
606 * background from a 3-D border, or filling in complicated
607 * defaults that couldn't be specified to Tk_ConfigureWidget.
610 if (mbPtr
->state
== tkActiveUid
) {
611 Tk_SetBackgroundFromBorder(mbPtr
->tkwin
, mbPtr
->activeBorder
);
613 Tk_SetBackgroundFromBorder(mbPtr
->tkwin
, mbPtr
->normalBorder
);
614 if ((mbPtr
->state
!= tkNormalUid
) && (mbPtr
->state
!= tkDisabledUid
)) {
615 Tcl_AppendResult(interp
, "bad state value \"", mbPtr
->state
,
616 "\": must be normal, active, or disabled", (char *) NULL
);
617 mbPtr
->state
= tkNormalUid
;
622 gcValues
.font
= mbPtr
->fontPtr
->fid
;
623 gcValues
.foreground
= mbPtr
->normalFg
->pixel
;
624 gcValues
.background
= Tk_3DBorderColor(mbPtr
->normalBorder
)->pixel
;
627 * Note: GraphicsExpose events are disabled in GC's because they're
628 * used to copy stuff from an off-screen pixmap onto the screen (we know
629 * that there's no problem with obscured areas).
632 gcValues
.graphics_exposures
= False
;
633 newGC
= Tk_GetGC(mbPtr
->tkwin
,
634 GCForeground
|GCBackground
|GCFont
|GCGraphicsExposures
, &gcValues
);
635 if (mbPtr
->normalTextGC
!= None
) {
636 Tk_FreeGC(mbPtr
->normalTextGC
);
638 mbPtr
->normalTextGC
= newGC
;
640 gcValues
.font
= mbPtr
->fontPtr
->fid
;
641 gcValues
.foreground
= mbPtr
->activeFg
->pixel
;
642 gcValues
.background
= Tk_3DBorderColor(mbPtr
->activeBorder
)->pixel
;
643 newGC
= Tk_GetGC(mbPtr
->tkwin
, GCForeground
|GCBackground
|GCFont
,
645 if (mbPtr
->activeTextGC
!= None
) {
646 Tk_FreeGC(mbPtr
->activeTextGC
);
648 mbPtr
->activeTextGC
= newGC
;
650 gcValues
.font
= mbPtr
->fontPtr
->fid
;
651 gcValues
.background
= Tk_3DBorderColor(mbPtr
->normalBorder
)->pixel
;
652 if (mbPtr
->disabledFg
!= NULL
) {
653 gcValues
.foreground
= mbPtr
->disabledFg
->pixel
;
654 mask
= GCForeground
|GCBackground
|GCFont
;
656 gcValues
.foreground
= gcValues
.background
;
657 if (mbPtr
->gray
== None
) {
658 mbPtr
->gray
= Tk_GetBitmap(interp
, mbPtr
->tkwin
,
659 Tk_GetUid("gray50"));
660 if (mbPtr
->gray
== None
) {
664 gcValues
.fill_style
= FillStippled
;
665 gcValues
.stipple
= mbPtr
->gray
;
666 mask
= GCForeground
|GCFillStyle
|GCStipple
;
668 newGC
= Tk_GetGC(mbPtr
->tkwin
, mask
, &gcValues
);
669 if (mbPtr
->disabledGC
!= None
) {
670 Tk_FreeGC(mbPtr
->disabledGC
);
672 mbPtr
->disabledGC
= newGC
;
674 if (mbPtr
->padX
< 0) {
677 if (mbPtr
->padY
< 0) {
682 * Set up a trace on the menu button's variable, then initialize
683 * the variable if it doesn't already exist, so that it can be
684 * accessed immediately from Tcl code without fear of
685 * "nonexistent variable" errors.
688 value
= Tcl_GetVar(interp
, mbPtr
->varName
, TCL_GLOBAL_ONLY
);
690 Tcl_SetVar(interp
, mbPtr
->varName
, "", TCL_GLOBAL_ONLY
);
692 Tcl_TraceVar(interp
, mbPtr
->varName
,
693 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
694 MenuButtonVarProc
, (ClientData
) mbPtr
);
697 * Set up a trace on the variable that determines what's displayed
698 * in the menu button, if such a trace has been requested.
701 if ((mbPtr
->bitmap
== None
) && (mbPtr
->textVarName
!= NULL
)) {
704 value
= Tcl_GetVar(interp
, mbPtr
->textVarName
, TCL_GLOBAL_ONLY
);
706 Tcl_SetVar(interp
, mbPtr
->textVarName
, mbPtr
->text
,
709 if (mbPtr
->text
!= NULL
) {
712 mbPtr
->text
= ckalloc((unsigned) (strlen(value
) + 1));
713 strcpy(mbPtr
->text
, value
);
715 Tcl_TraceVar(interp
, mbPtr
->textVarName
,
716 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
717 MenuButtonTextVarProc
, (ClientData
) mbPtr
);
721 * Recompute the geometry for the button.
724 ComputeMenuButtonGeometry(mbPtr
);
727 * Lastly, arrange for the button to be redisplayed.
730 if (Tk_IsMapped(mbPtr
->tkwin
) && !(mbPtr
->flags
& REDRAW_PENDING
)) {
731 Tk_DoWhenIdle(DisplayMenuButton
, (ClientData
) mbPtr
);
732 mbPtr
->flags
|= REDRAW_PENDING
;
739 *----------------------------------------------------------------------
741 * DisplayMenuButton --
743 * This procedure is invoked to display a menubutton widget.
749 * Commands are output to X to display the menubutton in its
752 *----------------------------------------------------------------------
757 ClientData clientData
/* Information about widget. */
760 register MenuButton
*mbPtr
= (MenuButton
*) clientData
;
764 int x
= 0; /* Initialization needed only to stop
765 * compiler warning. */
767 register Tk_Window tkwin
= mbPtr
->tkwin
;
769 mbPtr
->flags
&= ~REDRAW_PENDING
;
770 if ((mbPtr
->tkwin
== NULL
) || !Tk_IsMapped(tkwin
)) {
774 if ((mbPtr
->state
== tkDisabledUid
) && (mbPtr
->disabledFg
!= NULL
)) {
775 gc
= mbPtr
->disabledGC
;
776 border
= mbPtr
->normalBorder
;
777 } else if (mbPtr
->state
== tkActiveUid
) {
778 gc
= mbPtr
->activeTextGC
;
779 border
= mbPtr
->activeBorder
;
781 gc
= mbPtr
->normalTextGC
;
782 border
= mbPtr
->normalBorder
;
786 * In order to avoid screen flashes, this procedure redraws
787 * the menu button in a pixmap, then copies the pixmap to the
788 * screen in a single operation. This means that there's no
789 * point in time where the on-sreen image has been cleared.
792 pixmap
= XCreatePixmap(Tk_Display(tkwin
), Tk_WindowId(tkwin
),
793 Tk_Width(tkwin
), Tk_Height(tkwin
),
794 Tk_DefaultDepth(Tk_Screen(tkwin
)));
795 Tk_Fill3DRectangle(Tk_Display(tkwin
), pixmap
, border
,
796 0, 0, Tk_Width(tkwin
), Tk_Height(tkwin
), 0, TK_RELIEF_FLAT
);
799 * Display bitmap or text for button.
802 if (mbPtr
->bitmap
!= None
) {
803 unsigned int width
, height
;
805 #if defined(USE_XPM3)
806 Tk_SizeOfPixmap(mbPtr
->bitmap
, &width
, &height
);
808 Tk_SizeOfBitmap(mbPtr
->bitmap
, &width
, &height
);
810 switch (mbPtr
->anchor
) {
811 case TK_ANCHOR_NW
: case TK_ANCHOR_W
: case TK_ANCHOR_SW
:
812 x
+= mbPtr
->borderWidth
+ mbPtr
->padX
;
814 case TK_ANCHOR_N
: case TK_ANCHOR_CENTER
: case TK_ANCHOR_S
:
815 x
+= (Tk_Width(tkwin
) - width
)/2;
818 x
+= Tk_Width(tkwin
) - mbPtr
->borderWidth
- mbPtr
->padX
822 switch (mbPtr
->anchor
) {
823 case TK_ANCHOR_NW
: case TK_ANCHOR_N
: case TK_ANCHOR_NE
:
824 y
= mbPtr
->borderWidth
+ mbPtr
->padY
;
826 case TK_ANCHOR_W
: case TK_ANCHOR_CENTER
: case TK_ANCHOR_E
:
827 y
= (Tk_Height(tkwin
) - height
)/2;
830 y
= Tk_Height(tkwin
) - mbPtr
->borderWidth
- mbPtr
->padY
834 #if defined(USE_XPM3)
835 XCopyArea(Tk_Display(tkwin
), mbPtr
->bitmap
, pixmap
,
836 gc
, 0, 0, width
, height
, x
, y
);
838 XCopyPlane(Tk_Display(tkwin
), mbPtr
->bitmap
, pixmap
,
839 gc
, 0, 0, width
, height
, x
, y
, 1);
842 switch (mbPtr
->anchor
) {
843 case TK_ANCHOR_NW
: case TK_ANCHOR_W
: case TK_ANCHOR_SW
:
844 x
= mbPtr
->borderWidth
+ mbPtr
->padX
+ mbPtr
->leftBearing
;
846 case TK_ANCHOR_N
: case TK_ANCHOR_CENTER
: case TK_ANCHOR_S
:
847 x
= (Tk_Width(tkwin
) + mbPtr
->leftBearing
848 - mbPtr
->rightBearing
)/2;
851 x
= Tk_Width(tkwin
) - mbPtr
->borderWidth
- mbPtr
->padX
852 - mbPtr
->rightBearing
;
855 switch (mbPtr
->anchor
) {
856 case TK_ANCHOR_NW
: case TK_ANCHOR_N
: case TK_ANCHOR_NE
:
857 y
= mbPtr
->borderWidth
+ mbPtr
->fontPtr
->ascent
860 case TK_ANCHOR_W
: case TK_ANCHOR_CENTER
: case TK_ANCHOR_E
:
861 y
= (Tk_Height(tkwin
) + mbPtr
->fontPtr
->ascent
862 - mbPtr
->fontPtr
->descent
)/2;
865 y
= Tk_Height(tkwin
) - mbPtr
->borderWidth
- mbPtr
->padY
866 - mbPtr
->fontPtr
->descent
;
869 XDrawString(Tk_Display(tkwin
), pixmap
, gc
, x
, y
, mbPtr
->text
,
871 if (mbPtr
->underline
>= 0) {
872 TkUnderlineChars(Tk_Display(tkwin
), pixmap
, gc
, mbPtr
->fontPtr
,
873 mbPtr
->text
, x
, y
, TK_NEWLINES_NOT_SPECIAL
,
874 mbPtr
->underline
, mbPtr
->underline
);
879 * If the menu button is disabled with a stipple rather than a special
880 * foreground color, generate the stippled effect.
883 if ((mbPtr
->state
== tkDisabledUid
) && (mbPtr
->disabledFg
== NULL
)) {
884 XFillRectangle(Tk_Display(tkwin
), pixmap
, mbPtr
->disabledGC
,
885 mbPtr
->borderWidth
, mbPtr
->borderWidth
,
886 (unsigned) (Tk_Width(tkwin
) - 2*mbPtr
->borderWidth
),
887 (unsigned) (Tk_Height(tkwin
) - 2*mbPtr
->borderWidth
));
891 * Draw the border last. This way, if the menu button's contents
892 * overflow onto the border they'll be covered up by the border.
895 if (mbPtr
->relief
!= TK_RELIEF_FLAT
) {
896 Tk_Draw3DRectangle(Tk_Display(tkwin
), pixmap
, border
,
897 0, 0, Tk_Width(tkwin
), Tk_Height(tkwin
),
898 mbPtr
->borderWidth
, mbPtr
->relief
);
902 * Copy the information from the off-screen pixmap onto the screen,
903 * then delete the pixmap.
906 XCopyArea(Tk_Display(tkwin
), pixmap
, Tk_WindowId(tkwin
),
907 mbPtr
->normalTextGC
, 0, 0, Tk_Width(tkwin
), Tk_Height(tkwin
), 0, 0);
908 XFreePixmap(Tk_Display(tkwin
), pixmap
);
912 *--------------------------------------------------------------
914 * MenuButtonEventProc --
916 * This procedure is invoked by the Tk dispatcher for various
923 * When the window gets deleted, internal structures get
924 * cleaned up. When it gets exposed, it is redisplayed.
926 *--------------------------------------------------------------
930 MenuButtonEventProc (
931 ClientData clientData
, /* Information about window. */
932 XEvent
*eventPtr
/* Information about event. */
935 MenuButton
*mbPtr
= (MenuButton
*) clientData
;
936 if ((eventPtr
->type
== Expose
) && (eventPtr
->xexpose
.count
== 0)) {
937 if ((mbPtr
->tkwin
!= NULL
) && !(mbPtr
->flags
& REDRAW_PENDING
)) {
938 Tk_DoWhenIdle(DisplayMenuButton
, (ClientData
) mbPtr
);
939 mbPtr
->flags
|= REDRAW_PENDING
;
941 } else if (eventPtr
->type
== DestroyNotify
) {
942 Tcl_DeleteCommand(mbPtr
->interp
, Tk_PathName(mbPtr
->tkwin
));
945 * Careful! Must delete the event-sharing information here
946 * rather than in DestroyMenuButton. By the time that procedure
947 * is called the tkwin may have been reused, resulting in some
948 * other window accidentally being cut off from shared events.
951 Tk_UnshareEvents(mbPtr
->tkwin
, mbPtr
->varName
);
953 if (mbPtr
->flags
& REDRAW_PENDING
) {
954 Tk_CancelIdleCall(DisplayMenuButton
, (ClientData
) mbPtr
);
956 Tk_EventuallyFree((ClientData
) mbPtr
, DestroyMenuButton
);
961 *----------------------------------------------------------------------
963 * ComputeMenuButtonGeometry --
965 * After changes in a menu button's text or bitmap, this procedure
966 * recomputes the menu button's geometry and passes this information
967 * along to the geometry manager for the window.
973 * The menu button's window may change size.
975 *----------------------------------------------------------------------
979 ComputeMenuButtonGeometry (
980 register MenuButton
*mbPtr
/* Widget record for menu button. */
985 unsigned int width
, height
;
987 if (mbPtr
->bitmap
!= None
) {
988 #if defined(USE_XPM3)
989 Tk_SizeOfPixmap(mbPtr
->bitmap
, &width
, &height
);
991 Tk_SizeOfBitmap(mbPtr
->bitmap
, &width
, &height
);
993 if (mbPtr
->width
> 0) {
994 width
= mbPtr
->width
;
996 if (mbPtr
->height
> 0) {
997 height
= mbPtr
->height
;
1000 mbPtr
->textLength
= strlen(mbPtr
->text
);
1001 XTextExtents(mbPtr
->fontPtr
, mbPtr
->text
, mbPtr
->textLength
,
1002 &dummy
, &dummy
, &dummy
, &bbox
);
1003 mbPtr
->leftBearing
= bbox
.lbearing
;
1004 mbPtr
->rightBearing
= bbox
.rbearing
;
1005 width
= bbox
.lbearing
+ bbox
.rbearing
;
1006 height
= mbPtr
->fontPtr
->ascent
+ mbPtr
->fontPtr
->descent
;
1007 if (mbPtr
->width
> 0) {
1008 width
= mbPtr
->width
* XTextWidth(mbPtr
->fontPtr
, "0", 1);
1010 if (mbPtr
->height
> 0) {
1011 height
*= mbPtr
->height
;
1015 width
+= 2*mbPtr
->padX
;
1016 height
+= 2*mbPtr
->padY
;
1017 Tk_GeometryRequest(mbPtr
->tkwin
, (int) (width
+ 2*mbPtr
->borderWidth
),
1018 (int) (height
+ 2*mbPtr
->borderWidth
));
1019 Tk_SetInternalBorder(mbPtr
->tkwin
, mbPtr
->borderWidth
);
1023 *--------------------------------------------------------------
1025 * MenuButtonVarProc --
1027 * This procedure is invoked when someone changes the
1028 * state variable associated with a menubutton. This causes
1029 * the posted/unposted state of the menu to change if needed
1030 * to match the variable's new value.
1033 * NULL is always returned.
1036 * The menu may be posted or unposted.
1038 *--------------------------------------------------------------
1044 ClientData clientData
, /* Information about button. */
1045 Tcl_Interp
*interp
, /* Interpreter containing variable. */
1046 char *name1
, /* First part of variable's name. */
1047 char *name2
, /* Second part of variable's name. */
1048 int flags
/* Describes what's happening to variable. */
1051 register MenuButton
*mbPtr
= (MenuButton
*) clientData
;
1056 * If the variable is being unset, then just re-establish the
1057 * trace unless the whole interpreter is going away. Also unpost
1061 newFlags
= mbPtr
->flags
;
1062 if (flags
& TCL_TRACE_UNSETS
) {
1063 newFlags
&= ~POSTED
;
1064 if ((flags
& TCL_TRACE_DESTROYED
) && !(flags
& TCL_INTERP_DESTROYED
)) {
1065 Tcl_TraceVar2(interp
, name1
, name2
,
1066 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
1067 MenuButtonVarProc
, clientData
);
1072 * Use the value of the variable to update the posted status of
1076 value
= Tcl_GetVar2(interp
, name1
, name2
, flags
& TCL_GLOBAL_ONLY
);
1077 if (strcmp(value
, Tk_PathName(mbPtr
->tkwin
)) == 0) {
1080 newFlags
&= ~POSTED
;
1084 if ((mbPtr
->menuName
!= NULL
) && (newFlags
!= mbPtr
->flags
)) {
1085 mbPtr
->flags
= newFlags
;
1086 if (newFlags
& POSTED
) {
1091 * Post the menu just below the menu button.
1094 Tk_GetRootCoords(mbPtr
->tkwin
, &x
, &y
);
1095 y
+= Tk_Height(mbPtr
->tkwin
);
1096 sprintf(string
, "%d %d ", x
, y
);
1097 if (Tcl_VarEval(interp
, mbPtr
->menuName
, " post ", string
,
1098 mbPtr
->varName
, (char *) NULL
) != TCL_OK
) {
1099 TkBindError(interp
);
1102 if (Tcl_VarEval(interp
, mbPtr
->menuName
, " unpost",
1103 (char *) NULL
) != TCL_OK
) {
1104 TkBindError(interp
);
1108 return (char *) NULL
;
1112 *--------------------------------------------------------------
1114 * MenuButtonTextVarProc --
1116 * This procedure is invoked when someone changes the variable
1117 * whose contents are to be displayed in a menu button.
1120 * NULL is always returned.
1123 * The text displayed in the menu button will change to match the
1126 *--------------------------------------------------------------
1131 MenuButtonTextVarProc (
1132 ClientData clientData
, /* Information about button. */
1133 Tcl_Interp
*interp
, /* Interpreter containing variable. */
1134 char *name1
, /* Name of variable. */
1135 char *name2
, /* Second part of variable name. */
1136 int flags
/* Information about what happened. */
1139 register MenuButton
*mbPtr
= (MenuButton
*) clientData
;
1143 * If the variable is unset, then immediately recreate it unless
1144 * the whole interpreter is going away.
1147 if (flags
& TCL_TRACE_UNSETS
) {
1148 if ((flags
& TCL_TRACE_DESTROYED
) && !(flags
& TCL_INTERP_DESTROYED
)) {
1149 Tcl_SetVar2(interp
, name1
, name2
, mbPtr
->text
,
1150 flags
& TCL_GLOBAL_ONLY
);
1151 Tcl_TraceVar2(interp
, name1
, name2
,
1152 TCL_GLOBAL_ONLY
|TCL_TRACE_WRITES
|TCL_TRACE_UNSETS
,
1153 MenuButtonTextVarProc
, clientData
);
1155 return (char *) NULL
;
1158 value
= Tcl_GetVar2(interp
, name1
, name2
, flags
& TCL_GLOBAL_ONLY
);
1159 if (value
== NULL
) {
1162 if (mbPtr
->text
!= NULL
) {
1163 ckfree(mbPtr
->text
);
1165 mbPtr
->text
= ckalloc((unsigned) (strlen(value
) + 1));
1166 strcpy(mbPtr
->text
, value
);
1167 ComputeMenuButtonGeometry(mbPtr
);
1169 if ((mbPtr
->tkwin
!= NULL
) && Tk_IsMapped(mbPtr
->tkwin
)
1170 && !(mbPtr
->flags
& REDRAW_PENDING
)) {
1171 Tk_DoWhenIdle(DisplayMenuButton
, (ClientData
) mbPtr
);
1172 mbPtr
->flags
|= REDRAW_PENDING
;
1174 return (char *) NULL
;