]> git.zerfleddert.de Git - micropolis/blob - src/tk/tkmnbut.c
rename setenv and unsetenv to not clash with functions provided by libc
[micropolis] / src / tk / tkmnbut.c
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 \f
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 \f
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 \f
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 \f
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 \f
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 \f
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 \f
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 \f
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 \f
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 }
Impressum, Datenschutz