|
|
1.1 root 1: /*
2: * tkMenubutton.c --
3: *
4: * This module implements button-like widgets that are used
5: * to invoke pull-down menus.
6: *
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.
15: */
16:
17: #ifndef lint
18: static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkMenubutton.c,v 1.33 92/08/21 16:21:47 ouster Exp $ SPRITE (Berkeley)";
19: #endif
20:
21: #include "tkconfig.h"
22: #include "default.h"
23: #include "tkint.h"
24:
25: /*
26: * A data structure of the following type is kept for each
27: * widget managed by this file:
28: */
29:
30: typedef struct {
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
34: * cleaned up.*/
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).
38: * Malloc-ed. */
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
42: * menu group. */
43:
44: /*
45: * Information about what's displayed in the menu button:
46: */
47:
48: char *text; /* Text to display in button (malloc'ed)
49: * or NULL. */
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
57: * are ignored. */
58:
59: /*
60: * Information used when displaying widget:
61: */
62:
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
68: * border exists. */
69: Tk_3DBorder activeBorder; /* Structure used to draw 3-D
70: * border and background when window
71: * is active. NULL means no such
72: * border exists. */
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
81: * instead. */
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
92: * across it. */
93: int leftBearing; /* Amount text sticks left from its origin,
94: * in pixels. */
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
102: * on each side). */
103: Tk_Anchor anchor; /* Where text/bitmap should be displayed
104: * inside window region. */
105:
106: /*
107: * Miscellaneous information:
108: */
109:
110: Cursor cursor; /* Current cursor for window, or None. */
111: int flags; /* Various flags; see below for
112: * definitions. */
113: } MenuButton;
114:
115: /*
116: * Flag bits for buttons:
117: *
118: * REDRAW_PENDING: Non-zero means a DoWhenIdle handler
119: * has already been queued to redraw
120: * this window.
121: * POSTED: Non-zero means that the menu associated
122: * with this button has been posted (typically
123: * because of an active button press).
124: */
125:
126: #define REDRAW_PENDING 1
127: #define POSTED 2
128:
129: /*
130: * Information used for parsing configuration specs:
131: */
132:
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),
161: TK_CONFIG_NULL_OK},
162: #else
163: {TK_CONFIG_BITMAP, "-bitmap", "bitmap", "Bitmap",
164: DEF_MENUBUTTON_BITMAP, Tk_Offset(MenuButton, bitmap),
165: TK_CONFIG_NULL_OK},
166: #endif
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),
171: TK_CONFIG_NULL_OK},
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),
202: TK_CONFIG_NULL_OK},
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,
210: (char *) NULL, 0, 0}
211: };
212:
213: /*
214: * Forward declarations for procedures defined later in this file:
215: */
216:
217: static void ComputeMenuButtonGeometry _ANSI_ARGS_((
218: MenuButton *mbPtr));
219: static void MenuButtonEventProc _ANSI_ARGS_((ClientData clientData,
220: XEvent *eventPtr));
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,
226: int flags));
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,
231: int flags));
232: static void DestroyMenuButton _ANSI_ARGS_((ClientData clientData));
233: static void DisplayMenuButton _ANSI_ARGS_((ClientData clientData));
234:
235: /*
236: *--------------------------------------------------------------
237: *
238: * Tk_MenubuttonCmd --
239: *
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.
243: *
244: * Results:
245: * A standard Tcl result.
246: *
247: * Side effects:
248: * See the user documentation.
249: *
250: *--------------------------------------------------------------
251: */
252:
253: int
254: Tk_MenubuttonCmd(clientData, interp, argc, argv)
255: ClientData clientData; /* Main window associated with
256: * interpreter. */
257: Tcl_Interp *interp; /* Current interpreter. */
258: int argc; /* Number of arguments. */
259: char **argv; /* Argument strings. */
260: {
261: register MenuButton *mbPtr;
262: Tk_Window tkwin = (Tk_Window) clientData;
263: Tk_Window new;
264:
265: if (argc < 2) {
266: Tcl_AppendResult(interp, "wrong # args: should be \"",
267: argv[0], " pathName ?options?\"", (char *) NULL);
268: return TCL_ERROR;
269: }
270:
271: /*
272: * Create the new window.
273: */
274:
275: new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
276: if (new == NULL) {
277: return TCL_ERROR;
278: }
279:
280: /*
281: * Initialize the data structure for the button.
282: */
283:
284: mbPtr = (MenuButton *) ckalloc(sizeof(MenuButton));
285: mbPtr->tkwin = new;
286: mbPtr->interp = interp;
287: mbPtr->menuName = NULL;
288: mbPtr->varName = NULL;
289: mbPtr->text = 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;
304: mbPtr->gray = None;
305: mbPtr->disabledGC = NULL;
306: mbPtr->cursor = None;
307: mbPtr->flags = 0;
308:
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);
316: return TCL_ERROR;
317: }
318:
319: interp->result = Tk_PathName(mbPtr->tkwin);
320: return TCL_OK;
321: }
322:
323: /*
324: *--------------------------------------------------------------
325: *
326: * MenuButtonWidgetCmd --
327: *
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.
331: *
332: * Results:
333: * A standard Tcl result.
334: *
335: * Side effects:
336: * See the user documentation.
337: *
338: *--------------------------------------------------------------
339: */
340:
341: static int
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. */
347: {
348: register MenuButton *mbPtr = (MenuButton *) clientData;
349: int result = TCL_OK;
350: int length;
351: char c;
352:
353: if (argc < 2) {
354: Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
355: " option ?arg arg ...?\"", (char *) NULL);
356: return TCL_ERROR;
357: }
358: Tk_Preserve((ClientData) mbPtr);
359: c = argv[1][0];
360: length = strlen(argv[1]);
361: if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)) {
362: if (argc > 2) {
363: Tcl_AppendResult(interp, "wrong # args: should be \"",
364: argv[0], " activate\"", (char *) NULL);
365: goto error;
366: }
367: if (mbPtr->state != tkDisabledUid) {
368: mbPtr->state = tkActiveUid;
369: Tk_SetBackgroundFromBorder(mbPtr->tkwin, mbPtr->activeBorder);
370: goto redisplay;
371: }
372: } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
373: if (argc == 2) {
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);
379: } else {
380: result = ConfigureMenuButton(interp, mbPtr, argc-2, argv+2,
381: TK_CONFIG_ARGV_ONLY);
382: }
383: } else if ((c == 'd') && (strncmp(argv[1], "deactivate", length) == 0)) {
384: if (argc > 2) {
385: Tcl_AppendResult(interp, "wrong # args: should be \"",
386: argv[0], " deactivate\"", (char *) NULL);
387: goto error;
388: }
389: if (mbPtr->state != tkDisabledUid) {
390: mbPtr->state = tkNormalUid;
391: Tk_SetBackgroundFromBorder(mbPtr->tkwin, mbPtr->normalBorder);
392: goto redisplay;
393: }
394: } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0)) {
395: if (argc > 2) {
396: Tcl_AppendResult(interp, "wrong # args: should be \"",
397: argv[0], " post\"", (char *) NULL);
398: goto error;
399: }
400: if ((mbPtr->flags & POSTED) || (mbPtr->menuName == NULL)
401: || (mbPtr->state == tkDisabledUid)) {
402: goto done;
403: }
404:
405: /*
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.
409: */
410:
411: Tcl_SetVar(interp, mbPtr->varName, Tk_PathName(mbPtr->tkwin),
412: TCL_GLOBAL_ONLY);
413: } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) {
414: if (argc > 2) {
415: Tcl_AppendResult(interp, "wrong # args: should be \"",
416: argv[0], " unpost\"", (char *) NULL);
417: goto error;
418: }
419:
420: /*
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
428: * currently active.
429: */
430: Tcl_SetVar(interp, mbPtr->varName, "", TCL_GLOBAL_ONLY);
431: } else {
432: Tcl_AppendResult(interp, "bad option \"", argv[1],
433: "\": must be activate, configure, deactivate, ",
434: "post, or unpost", (char *) NULL);
435: goto error;
436: }
437: done:
438: Tk_Release((ClientData) mbPtr);
439: return result;
440:
441: redisplay:
442: if (Tk_IsMapped(mbPtr->tkwin) && !(mbPtr->flags & REDRAW_PENDING)) {
443: Tk_DoWhenIdle(DisplayMenuButton, (ClientData) mbPtr);
444: mbPtr->flags |= REDRAW_PENDING;
445: }
446: goto done;
447:
448: error:
449: Tk_Release((ClientData) mbPtr);
450: return TCL_ERROR;
451: }
452:
453: /*
454: *----------------------------------------------------------------------
455: *
456: * DestroyMenuButton --
457: *
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.
462: *
463: * Results:
464: * None.
465: *
466: * Side effects:
467: * Everything associated with the widget is freed up.
468: *
469: *----------------------------------------------------------------------
470: */
471:
472: static void
473: DestroyMenuButton(clientData)
474: ClientData clientData; /* Info about button widget. */
475: {
476: register MenuButton *mbPtr = (MenuButton *) clientData;
477: if (mbPtr->menuName != NULL) {
478: ckfree(mbPtr->menuName);
479: }
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);
484: }
485: if (mbPtr->text != NULL) {
486: ckfree(mbPtr->text);
487: }
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);
493: }
494: if (mbPtr->bitmap != None) {
495: #if defined(USE_XPM3)
496: Tk_FreePixmap(mbPtr->bitmap);
497: #else
498: Tk_FreeBitmap(mbPtr->bitmap);
499: #endif
500: }
501: if (mbPtr->normalBorder != NULL) {
502: Tk_Free3DBorder(mbPtr->normalBorder);
503: }
504: if (mbPtr->activeBorder != NULL) {
505: Tk_Free3DBorder(mbPtr->activeBorder);
506: }
507: if (mbPtr->fontPtr != NULL) {
508: Tk_FreeFontStruct(mbPtr->fontPtr);
509: }
510: if (mbPtr->normalFg != NULL) {
511: Tk_FreeColor(mbPtr->normalFg);
512: }
513: if (mbPtr->activeFg != NULL) {
514: Tk_FreeColor(mbPtr->activeFg);
515: }
516: if (mbPtr->disabledFg != NULL) {
517: Tk_FreeColor(mbPtr->disabledFg);
518: }
519: if (mbPtr->normalTextGC != None) {
520: Tk_FreeGC(mbPtr->normalTextGC);
521: }
522: if (mbPtr->activeTextGC != None) {
523: Tk_FreeGC(mbPtr->activeTextGC);
524: }
525: if (mbPtr->gray != None) {
526: Tk_FreeBitmap(mbPtr->gray);
527: }
528: if (mbPtr->disabledGC != None) {
529: Tk_FreeGC(mbPtr->disabledGC);
530: }
531: if (mbPtr->cursor != None) {
532: Tk_FreeCursor(mbPtr->cursor);
533: }
534: ckfree((char *) mbPtr);
535: }
536:
537: /*
538: *----------------------------------------------------------------------
539: *
540: * ConfigureMenuButton --
541: *
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.
545: *
546: * Results:
547: * The return value is a standard Tcl result. If TCL_ERROR is
548: * returned, then interp->result contains an error message.
549: *
550: * Side effects:
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.
554: *
555: *----------------------------------------------------------------------
556: */
557:
558: static int
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. */
566: {
567: XGCValues gcValues;
568: GC newGC;
569: unsigned long mask;
570: int result;
571: Tk_Uid oldGroup;
572: char *value;
573:
574: /*
575: * Eliminate any existing traces on variables monitored by the button.
576: */
577:
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);
582: }
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);
587: }
588:
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);
595: }
596: if (result != TCL_OK) {
597: return TCL_ERROR;
598: }
599:
600: /*
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.
604: */
605:
606: if (mbPtr->state == tkActiveUid) {
607: Tk_SetBackgroundFromBorder(mbPtr->tkwin, mbPtr->activeBorder);
608: } else {
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;
614: return TCL_ERROR;
615: }
616: }
617:
618: gcValues.font = mbPtr->fontPtr->fid;
619: gcValues.foreground = mbPtr->normalFg->pixel;
620: gcValues.background = Tk_3DBorderColor(mbPtr->normalBorder)->pixel;
621:
622: /*
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).
626: */
627:
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);
633: }
634: mbPtr->normalTextGC = newGC;
635:
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,
640: &gcValues);
641: if (mbPtr->activeTextGC != None) {
642: Tk_FreeGC(mbPtr->activeTextGC);
643: }
644: mbPtr->activeTextGC = newGC;
645:
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;
651: } else {
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) {
657: return TCL_ERROR;
658: }
659: }
660: gcValues.fill_style = FillStippled;
661: gcValues.stipple = mbPtr->gray;
662: mask = GCForeground|GCFillStyle|GCStipple;
663: }
664: newGC = Tk_GetGC(mbPtr->tkwin, mask, &gcValues);
665: if (mbPtr->disabledGC != None) {
666: Tk_FreeGC(mbPtr->disabledGC);
667: }
668: mbPtr->disabledGC = newGC;
669:
670: if (mbPtr->padX < 0) {
671: mbPtr->padX = 0;
672: }
673: if (mbPtr->padY < 0) {
674: mbPtr->padY = 0;
675: }
676:
677: /*
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.
682: */
683:
684: value = Tcl_GetVar(interp, mbPtr->varName, TCL_GLOBAL_ONLY);
685: if (value == NULL) {
686: Tcl_SetVar(interp, mbPtr->varName, "", TCL_GLOBAL_ONLY);
687: }
688: Tcl_TraceVar(interp, mbPtr->varName,
689: TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
690: MenuButtonVarProc, (ClientData) mbPtr);
691:
692: /*
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.
695: */
696:
697: if ((mbPtr->bitmap == None) && (mbPtr->textVarName != NULL)) {
698: char *value;
699:
700: value = Tcl_GetVar(interp, mbPtr->textVarName, TCL_GLOBAL_ONLY);
701: if (value == NULL) {
702: Tcl_SetVar(interp, mbPtr->textVarName, mbPtr->text,
703: TCL_GLOBAL_ONLY);
704: } else {
705: if (mbPtr->text != NULL) {
706: ckfree(mbPtr->text);
707: }
708: mbPtr->text = ckalloc((unsigned) (strlen(value) + 1));
709: strcpy(mbPtr->text, value);
710: }
711: Tcl_TraceVar(interp, mbPtr->textVarName,
712: TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
713: MenuButtonTextVarProc, (ClientData) mbPtr);
714: }
715:
716: /*
717: * Recompute the geometry for the button.
718: */
719:
720: ComputeMenuButtonGeometry(mbPtr);
721:
722: /*
723: * Lastly, arrange for the button to be redisplayed.
724: */
725:
726: if (Tk_IsMapped(mbPtr->tkwin) && !(mbPtr->flags & REDRAW_PENDING)) {
727: Tk_DoWhenIdle(DisplayMenuButton, (ClientData) mbPtr);
728: mbPtr->flags |= REDRAW_PENDING;
729: }
730:
731: return TCL_OK;
732: }
733:
734: /*
735: *----------------------------------------------------------------------
736: *
737: * DisplayMenuButton --
738: *
739: * This procedure is invoked to display a menubutton widget.
740: *
741: * Results:
742: * None.
743: *
744: * Side effects:
745: * Commands are output to X to display the menubutton in its
746: * current mode.
747: *
748: *----------------------------------------------------------------------
749: */
750:
751: static void
752: DisplayMenuButton(clientData)
753: ClientData clientData; /* Information about widget. */
754: {
755: register MenuButton *mbPtr = (MenuButton *) clientData;
756: GC gc;
757: Tk_3DBorder border;
758: Pixmap pixmap;
759: int x = 0; /* Initialization needed only to stop
760: * compiler warning. */
761: int y;
762: register Tk_Window tkwin = mbPtr->tkwin;
763:
764: mbPtr->flags &= ~REDRAW_PENDING;
765: if ((mbPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
766: return;
767: }
768:
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;
775: } else {
776: gc = mbPtr->normalTextGC;
777: border = mbPtr->normalBorder;
778: }
779:
780: /*
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.
785: */
786:
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);
792:
793: /*
794: * Display bitmap or text for button.
795: */
796:
797: if (mbPtr->bitmap != None) {
798: unsigned int width, height;
799:
800: #if defined(USE_XPM3)
801: Tk_SizeOfPixmap(mbPtr->bitmap, &width, &height);
802: #else
803: Tk_SizeOfBitmap(mbPtr->bitmap, &width, &height);
804: #endif
805: switch (mbPtr->anchor) {
806: case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW:
807: x += mbPtr->borderWidth + mbPtr->padX;
808: break;
809: case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S:
810: x += (Tk_Width(tkwin) - width)/2;
811: break;
812: default:
813: x += Tk_Width(tkwin) - mbPtr->borderWidth - mbPtr->padX
814: - width;
815: break;
816: }
817: switch (mbPtr->anchor) {
818: case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE:
819: y = mbPtr->borderWidth + mbPtr->padY;
820: break;
821: case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E:
822: y = (Tk_Height(tkwin) - height)/2;
823: break;
824: default:
825: y = Tk_Height(tkwin) - mbPtr->borderWidth - mbPtr->padY
826: - height;
827: break;
828: }
829: #if defined(USE_XPM3)
830: XCopyArea(Tk_Display(tkwin), mbPtr->bitmap, pixmap,
831: gc, 0, 0, width, height, x, y);
832: #else
833: XCopyPlane(Tk_Display(tkwin), mbPtr->bitmap, pixmap,
834: gc, 0, 0, width, height, x, y, 1);
835: #endif
836: } else {
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;
840: break;
841: case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S:
842: x = (Tk_Width(tkwin) + mbPtr->leftBearing
843: - mbPtr->rightBearing)/2;
844: break;
845: default:
846: x = Tk_Width(tkwin) - mbPtr->borderWidth - mbPtr->padX
847: - mbPtr->rightBearing;
848: break;
849: }
850: switch (mbPtr->anchor) {
851: case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE:
852: y = mbPtr->borderWidth + mbPtr->fontPtr->ascent
853: + mbPtr->padY;
854: break;
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;
858: break;
859: default:
860: y = Tk_Height(tkwin) - mbPtr->borderWidth - mbPtr->padY
861: - mbPtr->fontPtr->descent;
862: break;
863: }
864: XDrawString(Tk_Display(tkwin), pixmap, gc, x, y, mbPtr->text,
865: mbPtr->textLength);
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);
870: }
871: }
872:
873: /*
874: * If the menu button is disabled with a stipple rather than a special
875: * foreground color, generate the stippled effect.
876: */
877:
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));
883: }
884:
885: /*
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.
888: */
889:
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);
894: }
895:
896: /*
897: * Copy the information from the off-screen pixmap onto the screen,
898: * then delete the pixmap.
899: */
900:
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);
904: }
905:
906: /*
907: *--------------------------------------------------------------
908: *
909: * MenuButtonEventProc --
910: *
911: * This procedure is invoked by the Tk dispatcher for various
912: * events on buttons.
913: *
914: * Results:
915: * None.
916: *
917: * Side effects:
918: * When the window gets deleted, internal structures get
919: * cleaned up. When it gets exposed, it is redisplayed.
920: *
921: *--------------------------------------------------------------
922: */
923:
924: static void
925: MenuButtonEventProc(clientData, eventPtr)
926: ClientData clientData; /* Information about window. */
927: XEvent *eventPtr; /* Information about event. */
928: {
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;
934: }
935: } else if (eventPtr->type == DestroyNotify) {
936: Tcl_DeleteCommand(mbPtr->interp, Tk_PathName(mbPtr->tkwin));
937:
938: /*
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.
943: */
944:
945: Tk_UnshareEvents(mbPtr->tkwin, mbPtr->varName);
946: mbPtr->tkwin = NULL;
947: if (mbPtr->flags & REDRAW_PENDING) {
948: Tk_CancelIdleCall(DisplayMenuButton, (ClientData) mbPtr);
949: }
950: Tk_EventuallyFree((ClientData) mbPtr, DestroyMenuButton);
951: }
952: }
953:
954: /*
955: *----------------------------------------------------------------------
956: *
957: * ComputeMenuButtonGeometry --
958: *
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.
962: *
963: * Results:
964: * None.
965: *
966: * Side effects:
967: * The menu button's window may change size.
968: *
969: *----------------------------------------------------------------------
970: */
971:
972: static void
973: ComputeMenuButtonGeometry(mbPtr)
974: register MenuButton *mbPtr; /* Widget record for menu button. */
975: {
976: XCharStruct bbox;
977: int dummy;
978: unsigned int width, height;
979:
980: if (mbPtr->bitmap != None) {
981: #if defined(USE_XPM3)
982: Tk_SizeOfPixmap(mbPtr->bitmap, &width, &height);
983: #else
984: Tk_SizeOfBitmap(mbPtr->bitmap, &width, &height);
985: #endif
986: if (mbPtr->width > 0) {
987: width = mbPtr->width;
988: }
989: if (mbPtr->height > 0) {
990: height = mbPtr->height;
991: }
992: } else {
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);
1002: }
1003: if (mbPtr->height > 0) {
1004: height *= mbPtr->height;
1005: }
1006: }
1007:
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);
1013: }
1014:
1015: /*
1016: *--------------------------------------------------------------
1017: *
1018: * MenuButtonVarProc --
1019: *
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.
1024: *
1025: * Results:
1026: * NULL is always returned.
1027: *
1028: * Side effects:
1029: * The menu may be posted or unposted.
1030: *
1031: *--------------------------------------------------------------
1032: */
1033:
1034: /* ARGSUSED */
1035: static char *
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. */
1042: {
1043: register MenuButton *mbPtr = (MenuButton *) clientData;
1044: char *value;
1045: int newFlags;
1046:
1047: /*
1048: * If the variable is being unset, then just re-establish the
1049: * trace unless the whole interpreter is going away. Also unpost
1050: * the menu.
1051: */
1052:
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);
1060: }
1061: } else {
1062:
1063: /*
1064: * Use the value of the variable to update the posted status of
1065: * the menu.
1066: */
1067:
1068: value = Tcl_GetVar2(interp, name1, name2, flags & TCL_GLOBAL_ONLY);
1069: if (strcmp(value, Tk_PathName(mbPtr->tkwin)) == 0) {
1070: newFlags |= POSTED;
1071: } else {
1072: newFlags &= ~POSTED;
1073: }
1074: }
1075:
1076: if ((mbPtr->menuName != NULL) && (newFlags != mbPtr->flags)) {
1077: mbPtr->flags = newFlags;
1078: if (newFlags & POSTED) {
1079: int x, y;
1080: char string[50];
1081:
1082: /*
1083: * Post the menu just below the menu button.
1084: */
1085:
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);
1092: }
1093: } else {
1094: if (Tcl_VarEval(interp, mbPtr->menuName, " unpost",
1095: (char *) NULL) != TCL_OK) {
1096: TkBindError(interp);
1097: }
1098: }
1099: }
1100: return (char *) NULL;
1101: }
1102:
1103: /*
1104: *--------------------------------------------------------------
1105: *
1106: * MenuButtonTextVarProc --
1107: *
1108: * This procedure is invoked when someone changes the variable
1109: * whose contents are to be displayed in a menu button.
1110: *
1111: * Results:
1112: * NULL is always returned.
1113: *
1114: * Side effects:
1115: * The text displayed in the menu button will change to match the
1116: * variable.
1117: *
1118: *--------------------------------------------------------------
1119: */
1120:
1121: /* ARGSUSED */
1122: static char *
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. */
1129: {
1130: register MenuButton *mbPtr = (MenuButton *) clientData;
1131: char *value;
1132:
1133: /*
1134: * If the variable is unset, then immediately recreate it unless
1135: * the whole interpreter is going away.
1136: */
1137:
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);
1145: }
1146: return (char *) NULL;
1147: }
1148:
1149: value = Tcl_GetVar2(interp, name1, name2, flags & TCL_GLOBAL_ONLY);
1150: if (value == NULL) {
1151: value = "";
1152: }
1153: if (mbPtr->text != NULL) {
1154: ckfree(mbPtr->text);
1155: }
1156: mbPtr->text = ckalloc((unsigned) (strlen(value) + 1));
1157: strcpy(mbPtr->text, value);
1158: ComputeMenuButtonGeometry(mbPtr);
1159:
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;
1164: }
1165: return (char *) NULL;
1166: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.