]> git.zerfleddert.de Git - micropolis/blob - src/tk/tkmenu.c
XINCLUDE: use /usr/X11R6/include everywhere
[micropolis] / src / tk / tkmenu.c
1 /*
2 * tkMenu.c --
3 *
4 * This module implements menus for the Tk toolkit. The menus
5 * support normal button entries, plus check buttons, radio
6 * buttons, iconic forms of all of the above, and separator
7 * entries.
8 *
9 * Copyright 1990-1992 Regents of the University of California.
10 * Permission to use, copy, modify, and distribute this
11 * software and its documentation for any purpose and without
12 * fee is hereby granted, provided that the above copyright
13 * notice appear in all copies. The University of California
14 * makes no representations about the suitability of this
15 * software for any purpose. It is provided "as is" without
16 * express or implied warranty.
17 */
18
19 #ifndef lint
20 static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkMenu.c,v 1.37 92/08/24 09:24:04 ouster Exp $ SPRITE (Berkeley)";
21 #endif
22
23 #include "tkconfig.h"
24 #include "default.h"
25 #include "tkint.h"
26
27 /*
28 * One of the following data structures is kept for each entry of each
29 * menu managed by this file:
30 */
31
32 typedef struct MenuEntry {
33 int type; /* Type of menu entry; see below for
34 * valid types. */
35 struct Menu *menuPtr; /* Menu with which this entry is associated. */
36 char *label; /* Main text label displayed in entry (NULL
37 * if no label). Malloc'ed. */
38 int labelLength; /* Number of non-NULL characters in label. */
39 int underline; /* Index of character to underline. */
40 Pixmap bitmap; /* Bitmap to display in menu entry, or None.
41 * If not None then label is ignored. */
42 char *accel; /* Accelerator string displayed at right
43 * of menu entry. NULL means no such
44 * accelerator. Malloc'ed. */
45 int accelLength; /* Number of non-NULL characters in
46 * accelerator. */
47
48 /*
49 * Information related to displaying entry:
50 */
51
52 Tk_Uid state; /* State of button for display purposes:
53 * normal, active, or disabled. */
54 int height; /* Number of pixels occupied by entry in
55 * vertical dimension. */
56 int y; /* Y-coordinate of topmost pixel in entry. */
57 int selectorDiameter; /* Size of selector display, in pixels. */
58 Tk_3DBorder border; /* Structure used to draw background for
59 * entry. NULL means use overall border
60 * for menu. */
61 Tk_3DBorder activeBorder; /* Used to draw background and border when
62 * element is active. NULL means use
63 * activeBorder from menu. */
64 XFontStruct *fontPtr; /* Text font for menu entries. NULL means
65 * use overall font for menu. */
66 GC textGC; /* GC for drawing text in entry. NULL means
67 * use overall textGC for menu. */
68 GC activeGC; /* GC for drawing text in entry when active.
69 * NULL means use overall activeGC for
70 * menu. */
71 GC disabledGC; /* Used to produce disabled effect for entry.
72 * NULL means use overall disabledGC from
73 * menu structure. See comments for
74 * disabledFg in menu structure for more
75 * information. */
76
77 /*
78 * Information used to implement this entry's action:
79 */
80
81 char *command; /* Command to invoke when entry is invoked.
82 * Malloc'ed. */
83 char *name; /* Name of variable (for check buttons and
84 * radio buttons) or menu (for cascade
85 * entries). Malloc'ed.*/
86 char *onValue; /* Value to store in variable when selected
87 * (only for radio and check buttons).
88 * Malloc'ed. */
89 char *offValue; /* Value to store in variable when not
90 * selected (only for check buttons).
91 * Malloc'ed. */
92
93 /*
94 * Miscellaneous information:
95 */
96
97 int flags; /* Various flags. See below for definitions. */
98 } MenuEntry;
99
100 /*
101 * Flag values defined for menu entries:
102 *
103 * ENTRY_SELECTED: Non-zero means this is a radio or check
104 * button and that it should be drawn in
105 * the "selected" state.
106 * ENTRY_NEEDS_REDISPLAY: Non-zero means the entry should be redisplayed.
107 */
108
109 #define ENTRY_SELECTED 1
110 #define ENTRY_NEEDS_REDISPLAY 4
111
112 /*
113 * Types defined for MenuEntries:
114 */
115
116 #define COMMAND_ENTRY 0
117 #define SEPARATOR_ENTRY 1
118 #define CHECK_BUTTON_ENTRY 2
119 #define RADIO_BUTTON_ENTRY 3
120 #define CASCADE_ENTRY 4
121
122 /*
123 * Mask bits for above types:
124 */
125
126 #define COMMAND_MASK TK_CONFIG_USER_BIT
127 #define SEPARATOR_MASK (TK_CONFIG_USER_BIT << 1)
128 #define CHECK_BUTTON_MASK (TK_CONFIG_USER_BIT << 2)
129 #define RADIO_BUTTON_MASK (TK_CONFIG_USER_BIT << 3)
130 #define CASCADE_MASK (TK_CONFIG_USER_BIT << 4)
131 #define ALL_MASK (COMMAND_MASK | SEPARATOR_MASK \
132 | CHECK_BUTTON_MASK | RADIO_BUTTON_MASK | CASCADE_MASK)
133
134 /*
135 * Configuration specs for individual menu entries:
136 */
137
138 static Tk_ConfigSpec entryConfigSpecs[] = {
139 {TK_CONFIG_BORDER, "-activebackground", (char *) NULL, (char *) NULL,
140 DEF_MENU_ENTRY_ACTIVE_BG, Tk_Offset(MenuEntry, activeBorder),
141 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
142 |TK_CONFIG_NULL_OK},
143 {TK_CONFIG_STRING, "-accelerator", (char *) NULL, (char *) NULL,
144 DEF_MENU_ENTRY_ACCELERATOR, Tk_Offset(MenuEntry, accel),
145 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK},
146 {TK_CONFIG_BORDER, "-background", (char *) NULL, (char *) NULL,
147 DEF_MENU_ENTRY_BG, Tk_Offset(MenuEntry, border),
148 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
149 |TK_CONFIG_NULL_OK},
150 #if defined(USE_XPM3)
151 {TK_CONFIG_PIXMAP, "-bitmap", (char *) NULL, (char *) NULL,
152 DEF_MENU_ENTRY_BITMAP, Tk_Offset(MenuEntry, bitmap),
153 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
154 |TK_CONFIG_NULL_OK},
155 #else
156 {TK_CONFIG_BITMAP, "-bitmap", (char *) NULL, (char *) NULL,
157 DEF_MENU_ENTRY_BITMAP, Tk_Offset(MenuEntry, bitmap),
158 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
159 |TK_CONFIG_NULL_OK},
160 #endif
161 {TK_CONFIG_STRING, "-command", (char *) NULL, (char *) NULL,
162 DEF_MENU_ENTRY_COMMAND, Tk_Offset(MenuEntry, command),
163 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK},
164 {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
165 DEF_MENU_ENTRY_FONT, Tk_Offset(MenuEntry, fontPtr),
166 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
167 |TK_CONFIG_NULL_OK},
168 {TK_CONFIG_STRING, "-label", (char *) NULL, (char *) NULL,
169 DEF_MENU_ENTRY_LABEL, Tk_Offset(MenuEntry, label),
170 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK},
171 {TK_CONFIG_STRING, "-menu", (char *) NULL, (char *) NULL,
172 DEF_MENU_ENTRY_MENU, Tk_Offset(MenuEntry, name), CASCADE_MASK},
173 {TK_CONFIG_STRING, "-offvalue", (char *) NULL, (char *) NULL,
174 DEF_MENU_ENTRY_OFF_VALUE, Tk_Offset(MenuEntry, offValue),
175 CHECK_BUTTON_MASK},
176 {TK_CONFIG_UID, "-state", (char *) NULL, (char *) NULL,
177 DEF_MENU_ENTRY_STATE, Tk_Offset(MenuEntry, state),
178 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
179 |TK_CONFIG_DONT_SET_DEFAULT},
180 {TK_CONFIG_STRING, "-onvalue", (char *) NULL, (char *) NULL,
181 DEF_MENU_ENTRY_ON_VALUE, Tk_Offset(MenuEntry, onValue),
182 CHECK_BUTTON_MASK},
183 {TK_CONFIG_STRING, "-value", (char *) NULL, (char *) NULL,
184 DEF_MENU_ENTRY_VALUE, Tk_Offset(MenuEntry, onValue),
185 RADIO_BUTTON_MASK},
186 {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
187 DEF_MENU_ENTRY_CHECK_VARIABLE, Tk_Offset(MenuEntry, name),
188 CHECK_BUTTON_MASK},
189 {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
190 DEF_MENU_ENTRY_RADIO_VARIABLE, Tk_Offset(MenuEntry, name),
191 RADIO_BUTTON_MASK},
192 {TK_CONFIG_INT, "-underline", (char *) NULL, (char *) NULL,
193 DEF_MENU_ENTRY_UNDERLINE, Tk_Offset(MenuEntry, underline),
194 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
195 |TK_CONFIG_DONT_SET_DEFAULT},
196 {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
197 (char *) NULL, 0, 0}
198 };
199
200 /*
201 * A data structure of the following type is kept for each
202 * menu managed by this file:
203 */
204
205 typedef struct Menu {
206 Tk_Window tkwin; /* Window that embodies the pane. NULL
207 * means that the window has been destroyed
208 * but the data structures haven't yet been
209 * cleaned up.*/
210 Tcl_Interp *interp; /* Interpreter associated with menu. */
211 MenuEntry **entries; /* Array of pointers to all the entries
212 * in the menu. NULL means no entries. */
213 int numEntries; /* Number of elements in entries. */
214 int active; /* Index of active entry. -1 means
215 * nothing active. */
216 Tk_Uid group; /* Used to allow event sharing between
217 * related menus and menu buttons. */
218
219 /*
220 * Information used when displaying widget:
221 */
222
223 Tk_3DBorder border; /* Structure used to draw 3-D
224 * border and background for menu. */
225 int borderWidth; /* Width of border around whole menu. */
226 Tk_3DBorder activeBorder; /* Used to draw background and border for
227 * active element (if any). */
228 int activeBorderWidth; /* Width of border around active element. */
229 XFontStruct *fontPtr; /* Text font for menu entries. */
230 XColor *fg; /* Foreground color for entries. */
231 GC textGC; /* GC for drawing text and other features
232 * of menu entries. */
233 XColor *disabledFg; /* Foreground color when disabled. NULL
234 * means use normalFg with a 50% stipple
235 * instead. */
236 Pixmap gray; /* Bitmap for drawing disabled entries in
237 * a stippled fashion. None means not
238 * allocated yet. */
239 GC disabledGC; /* Used to produce disabled effect. If
240 * disabledFg isn't NULL, this GC is used to
241 * draw text and icons for disabled entries.
242 * Otherwise text and icons are drawn with
243 * normalGC and this GC is used to stipple
244 * background across them. */
245 XColor *activeFg; /* Foreground color for active entry. */
246 GC activeGC; /* GC for drawing active entry. */
247 XColor *selectorFg; /* Color for selectors in radio and check
248 * button entries. */
249 GC selectorGC; /* For drawing selectors. */
250 int selectorSpace; /* Number of pixels to allow for displaying
251 * selectors in menu entries (includes extra
252 * space around selector). */
253 int labelWidth; /* Number of pixels to allow for displaying
254 * labels in menu entries. */
255
256 /*
257 * Miscellaneous information:
258 */
259
260 Cursor cursor; /* Current cursor for window, or None. */
261 MenuEntry *postedCascade; /* Points to menu entry for cascaded
262 * submenu that is currently posted, or
263 * NULL if no submenu posted. */
264 int flags; /* Various flags; see below for
265 * definitions. */
266 } Menu;
267
268 /*
269 * Flag bits for menus:
270 *
271 * REDRAW_PENDING: Non-zero means a DoWhenIdle handler
272 * has already been queued to redraw
273 * this window.
274 * RESIZE_PENDING: Non-zero means a call to ComputeMenuGeometry
275 * has already been scheduled.
276 */
277
278 #define REDRAW_PENDING 1
279 #define RESIZE_PENDING 2
280
281 /*
282 * Configuration specs valid for the menu as a whole:
283 */
284
285 static Tk_ConfigSpec configSpecs[] = {
286 {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
287 DEF_MENU_ACTIVE_BG_COLOR, Tk_Offset(Menu, activeBorder),
288 TK_CONFIG_COLOR_ONLY},
289 {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
290 DEF_MENU_ACTIVE_BG_MONO, Tk_Offset(Menu, activeBorder),
291 TK_CONFIG_MONO_ONLY},
292 {TK_CONFIG_PIXELS, "-activeborderwidth", "activeBorderWidth", "BorderWidth",
293 DEF_MENU_ACTIVE_BORDER_WIDTH, Tk_Offset(Menu, activeBorderWidth), 0},
294 {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
295 DEF_MENU_ACTIVE_FG_COLOR, Tk_Offset(Menu, activeFg),
296 TK_CONFIG_COLOR_ONLY},
297 {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
298 DEF_MENU_ACTIVE_FG_MONO, Tk_Offset(Menu, activeFg),
299 TK_CONFIG_MONO_ONLY},
300 {TK_CONFIG_BORDER, "-background", "background", "Background",
301 DEF_MENU_BG_COLOR, Tk_Offset(Menu, border), TK_CONFIG_COLOR_ONLY},
302 {TK_CONFIG_BORDER, "-background", "background", "Background",
303 DEF_MENU_BG_MONO, Tk_Offset(Menu, border), TK_CONFIG_MONO_ONLY},
304 {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
305 (char *) NULL, 0, 0},
306 {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
307 (char *) NULL, 0, 0},
308 {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
309 DEF_MENU_BORDER_WIDTH, Tk_Offset(Menu, borderWidth), 0},
310 {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
311 DEF_MENU_CURSOR, Tk_Offset(Menu, cursor), TK_CONFIG_NULL_OK},
312 {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
313 "DisabledForeground", DEF_MENU_DISABLED_FG_COLOR,
314 Tk_Offset(Menu, disabledFg), TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
315 {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
316 "DisabledForeground", DEF_MENU_DISABLED_FG_MONO,
317 Tk_Offset(Menu, disabledFg), TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
318 {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
319 (char *) NULL, 0, 0},
320 {TK_CONFIG_FONT, "-font", "font", "Font",
321 DEF_MENU_FONT, Tk_Offset(Menu, fontPtr), 0},
322 {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
323 DEF_MENU_FG, Tk_Offset(Menu, fg), 0},
324 {TK_CONFIG_COLOR, "-selector", "selector", "Foreground",
325 DEF_MENU_SELECTOR_COLOR, Tk_Offset(Menu, selectorFg),
326 TK_CONFIG_COLOR_ONLY},
327 {TK_CONFIG_COLOR, "-selector", "selector", "Foreground",
328 DEF_MENU_SELECTOR_MONO, Tk_Offset(Menu, selectorFg),
329 TK_CONFIG_MONO_ONLY},
330 {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
331 (char *) NULL, 0, 0}
332 };
333
334 /*
335 * Forward declarations for procedures defined later in this file:
336 */
337
338 static int ActivateMenuEntry _ANSI_ARGS_((Menu *menuPtr,
339 int index));
340 static void ComputeMenuGeometry _ANSI_ARGS_((
341 ClientData clientData));
342 static int ConfigureMenu _ANSI_ARGS_((Tcl_Interp *interp,
343 Menu *menuPtr, int argc, char **argv,
344 int flags));
345 static int ConfigureMenuEntry _ANSI_ARGS_((Tcl_Interp *interp,
346 Menu *menuPtr, MenuEntry *mePtr, int index,
347 int argc, char **argv, int flags));
348 static void DestroyMenu _ANSI_ARGS_((ClientData clientData));
349 static void DestroyMenuEntry _ANSI_ARGS_((ClientData clientData));
350 static void DisplayMenu _ANSI_ARGS_((ClientData clientData));
351 static void EventuallyRedrawMenu _ANSI_ARGS_((Menu *menuPtr,
352 int index));
353 static int GetMenuIndex _ANSI_ARGS_((Tcl_Interp *interp,
354 Menu *menuPtr, char *string, int *indexPtr));
355 static void MenuEventProc _ANSI_ARGS_((ClientData clientData,
356 XEvent *eventPtr));
357 static char * MenuVarProc _ANSI_ARGS_((ClientData clientData,
358 Tcl_Interp *interp, char *name1, char *name2,
359 int flags));
360 static int MenuWidgetCmd _ANSI_ARGS_((ClientData clientData,
361 Tcl_Interp *interp, int argc, char **argv));
362 static int PostSubmenu _ANSI_ARGS_((Tcl_Interp *interp,
363 Menu *menuPtr, MenuEntry *mePtr));
364 \f
365 /*
366 *--------------------------------------------------------------
367 *
368 * Tk_MenuCmd --
369 *
370 * This procedure is invoked to process the "menu" Tcl
371 * command. See the user documentation for details on
372 * what it does.
373 *
374 * Results:
375 * A standard Tcl result.
376 *
377 * Side effects:
378 * See the user documentation.
379 *
380 *--------------------------------------------------------------
381 */
382
383 int
384 Tk_MenuCmd(clientData, interp, argc, argv)
385 ClientData clientData; /* Main window associated with
386 * interpreter. */
387 Tcl_Interp *interp; /* Current interpreter. */
388 int argc; /* Number of arguments. */
389 char **argv; /* Argument strings. */
390 {
391 Tk_Window tkwin = (Tk_Window) clientData;
392 Tk_Window new;
393 register Menu *menuPtr;
394 XSetWindowAttributes atts;
395
396 if (argc < 2) {
397 Tcl_AppendResult(interp, "wrong # args: should be \"",
398 argv[0], " pathName ?options?\"", (char *) NULL);
399 return TCL_ERROR;
400 }
401
402 /*
403 * Create the new window. Set override-redirect so the window
404 * manager won't add a border or argue about placement, and set
405 * save-under so that the window can pop up and down without a
406 * lot of re-drawing.
407 */
408
409 new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], "");
410 if (new == NULL) {
411 return TCL_ERROR;
412 }
413 atts.override_redirect = True;
414 atts.save_under = True;
415 Tk_ChangeWindowAttributes(new, CWOverrideRedirect|CWSaveUnder, &atts);
416
417 /*
418 * Initialize the data structure for the menu.
419 */
420
421 menuPtr = (Menu *) ckalloc(sizeof(Menu));
422 menuPtr->tkwin = new;
423 menuPtr->interp = interp;
424 menuPtr->entries = NULL;
425 menuPtr->numEntries = 0;
426 menuPtr->active = -1;
427 menuPtr->group = NULL;
428 menuPtr->border = NULL;
429 menuPtr->activeBorder = NULL;
430 menuPtr->fontPtr = NULL;
431 menuPtr->fg = NULL;
432 menuPtr->textGC = None;
433 menuPtr->disabledFg = NULL;
434 menuPtr->gray = None;
435 menuPtr->disabledGC = None;
436 menuPtr->activeFg = NULL;
437 menuPtr->activeGC = None;
438 menuPtr->selectorFg = NULL;
439 menuPtr->selectorGC = None;
440 menuPtr->cursor = None;
441 menuPtr->postedCascade = NULL;
442 menuPtr->flags = 0;
443
444 Tk_SetClass(new, "Menu");
445 Tk_CreateEventHandler(menuPtr->tkwin, ExposureMask|StructureNotifyMask,
446 MenuEventProc, (ClientData) menuPtr);
447 Tcl_CreateCommand(interp, Tk_PathName(menuPtr->tkwin), MenuWidgetCmd,
448 (ClientData) menuPtr, (void (*)()) NULL);
449 if (ConfigureMenu(interp, menuPtr, argc-2, argv+2, 0) != TCL_OK) {
450 goto error;
451 }
452
453 interp->result = Tk_PathName(menuPtr->tkwin);
454 return TCL_OK;
455
456 error:
457 Tk_DestroyWindow(menuPtr->tkwin);
458 return TCL_ERROR;
459 }
460 \f
461 /*
462 *--------------------------------------------------------------
463 *
464 * MenuWidgetCmd --
465 *
466 * This procedure is invoked to process the Tcl command
467 * that corresponds to a widget managed by this module.
468 * See the user documentation for details on what it does.
469 *
470 * Results:
471 * A standard Tcl result.
472 *
473 * Side effects:
474 * See the user documentation.
475 *
476 *--------------------------------------------------------------
477 */
478
479 static int
480 MenuWidgetCmd(clientData, interp, argc, argv)
481 ClientData clientData; /* Information about menu widget. */
482 Tcl_Interp *interp; /* Current interpreter. */
483 int argc; /* Number of arguments. */
484 char **argv; /* Argument strings. */
485 {
486 register Menu *menuPtr = (Menu *) clientData;
487 register MenuEntry *mePtr;
488 int result = TCL_OK;
489 int length, type;
490 char c;
491
492 if (argc < 2) {
493 Tcl_AppendResult(interp, "wrong # args: should be \"",
494 argv[0], " option ?arg arg ...?\"", (char *) NULL);
495 return TCL_ERROR;
496 }
497 Tk_Preserve((ClientData) menuPtr);
498 c = argv[1][0];
499 length = strlen(argv[1]);
500 if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)
501 && (length >= 2)) {
502 int index;
503
504 if (argc != 3) {
505 Tcl_AppendResult(interp, "wrong # args: should be \"",
506 argv[0], " activate index\"", (char *) NULL);
507 goto error;
508 }
509 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
510 goto error;
511 }
512 if (menuPtr->active == index) {
513 goto done;
514 }
515 if (index >= 0) {
516 if ((menuPtr->entries[index]->type == SEPARATOR_ENTRY)
517 || (menuPtr->entries[index]->state == tkDisabledUid)) {
518 index = -1;
519 }
520 }
521 result = ActivateMenuEntry(menuPtr, index);
522 } else if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)
523 && (length >= 2)) {
524 MenuEntry **newEntries;
525
526 if (argc < 3) {
527 Tcl_AppendResult(interp, "wrong # args: should be \"",
528 argv[0], " add type ?options?\"", (char *) NULL);
529 goto error;
530 }
531
532 /*
533 * Figure out the type of the new entry.
534 */
535
536 c = argv[2][0];
537 length = strlen(argv[2]);
538 if ((c == 'c') && (strncmp(argv[2], "cascade", length) == 0)
539 && (length >= 2)) {
540 type = CASCADE_ENTRY;
541 } else if ((c == 'c') && (strncmp(argv[2], "checkbutton", length) == 0)
542 && (length >= 2)) {
543 type = CHECK_BUTTON_ENTRY;
544 } else if ((c == 'c') && (strncmp(argv[2], "command", length) == 0)
545 && (length >= 2)) {
546 type = COMMAND_ENTRY;
547 } else if ((c == 'r')
548 && (strncmp(argv[2], "radiobutton", length) == 0)) {
549 type = RADIO_BUTTON_ENTRY;
550 } else if ((c == 's')
551 && (strncmp(argv[2], "separator", length) == 0)) {
552 type = SEPARATOR_ENTRY;
553 } else {
554 Tcl_AppendResult(interp, "bad menu entry type \"",
555 argv[2], "\": must be cascade, checkbutton, ",
556 "command, radiobutton, or separator", (char *) NULL);
557 goto error;
558 }
559
560 /*
561 * Add a new entry to the end of the menu's array of entries,
562 * and process options for it.
563 */
564
565 mePtr = (MenuEntry *) ckalloc(sizeof(MenuEntry));
566 newEntries = (MenuEntry **) ckalloc((unsigned)
567 ((menuPtr->numEntries+1)*sizeof(MenuEntry *)));
568 if (menuPtr->numEntries != 0) {
569 memcpy((VOID *) newEntries, (VOID *) menuPtr->entries,
570 menuPtr->numEntries*sizeof(MenuEntry *));
571 ckfree((char *) menuPtr->entries);
572 }
573 menuPtr->entries = newEntries;
574 menuPtr->entries[menuPtr->numEntries] = mePtr;
575 menuPtr->numEntries++;
576 mePtr->type = type;
577 mePtr->menuPtr = menuPtr;
578 mePtr->label = NULL;
579 mePtr->underline = -1;
580 mePtr->bitmap = None;
581 mePtr->accel = NULL;
582 mePtr->state = tkNormalUid;
583 mePtr->border = NULL;
584 mePtr->activeBorder = NULL;
585 mePtr->fontPtr = NULL;
586 mePtr->textGC = None;
587 mePtr->activeGC = None;
588 mePtr->disabledGC = None;
589 mePtr->command = NULL;
590 mePtr->name = NULL;
591 mePtr->onValue = NULL;
592 mePtr->offValue = NULL;
593 mePtr->flags = 0;
594 if (ConfigureMenuEntry(interp, menuPtr, mePtr, menuPtr->numEntries-1,
595 argc-3, argv+3, 0) != TCL_OK) {
596 DestroyMenuEntry((ClientData) mePtr);
597 menuPtr->numEntries--;
598 goto error;
599 }
600 } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
601 if (argc == 2) {
602 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
603 (char *) menuPtr, (char *) NULL, 0);
604 } else if (argc == 3) {
605 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
606 (char *) menuPtr, argv[2], 0);
607 } else {
608 result = ConfigureMenu(interp, menuPtr, argc-2, argv+2,
609 TK_CONFIG_ARGV_ONLY);
610 }
611 } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
612 && (length >= 2)) {
613 int index, i;
614
615 if (argc != 3) {
616 Tcl_AppendResult(interp, "wrong # args: should be \"",
617 argv[0], " delete index\"", (char *) NULL);
618 goto error;
619 }
620 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
621 goto error;
622 }
623 if (index < 0) {
624 goto done;
625 }
626 Tk_EventuallyFree((ClientData) menuPtr->entries[index],
627 DestroyMenuEntry);
628 for (i = index; i < menuPtr->numEntries-1; i++) {
629 menuPtr->entries[i] = menuPtr->entries[i+1];
630 }
631 menuPtr->numEntries -= 1;
632 if (menuPtr->active == index) {
633 menuPtr->active = -1;
634 } else if (menuPtr->active > index) {
635 menuPtr->active -= 1;
636 }
637 if (!(menuPtr->flags & RESIZE_PENDING)) {
638 menuPtr->flags |= RESIZE_PENDING;
639 Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
640 }
641 } else if ((c == 'd') && (strncmp(argv[1], "disable", length) == 0)
642 && (length >= 2)) {
643 int index;
644
645 if (argc != 3) {
646 Tcl_AppendResult(interp, "wrong # args: should be \"",
647 argv[0], " disable index\"", (char *) NULL);
648 goto error;
649 }
650 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
651 goto error;
652 }
653 if (index < 0) {
654 goto done;
655 }
656 menuPtr->entries[index]->state = tkDisabledUid;
657 if (menuPtr->active == index) {
658 menuPtr->active = -1;
659 }
660 EventuallyRedrawMenu(menuPtr, index);
661 } else if ((c == 'e') && (length >= 3)
662 && (strncmp(argv[1], "enable", length) == 0)) {
663 int index;
664
665 if (argc != 3) {
666 Tcl_AppendResult(interp, "wrong # args: should be \"",
667 argv[0], " enable index\"", (char *) NULL);
668 goto error;
669 }
670 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
671 goto error;
672 }
673 if (index < 0) {
674 goto done;
675 }
676 menuPtr->entries[index]->state = tkNormalUid;
677 EventuallyRedrawMenu(menuPtr, index);
678 } else if ((c == 'e') && (length >= 3)
679 && (strncmp(argv[1], "entryconfigure", length) == 0)) {
680 int index;
681
682 if (argc < 3) {
683 Tcl_AppendResult(interp, "wrong # args: should be \"",
684 argv[0], " entryconfigure index ?option value ...?\"",
685 (char *) NULL);
686 goto error;
687 }
688 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
689 goto error;
690 }
691 if (index < 0) {
692 goto done;
693 }
694 mePtr = menuPtr->entries[index];
695 Tk_Preserve((ClientData) mePtr);
696 if (argc == 3) {
697 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
698 (char *) mePtr, (char *) NULL,
699 COMMAND_MASK << mePtr->type);
700 } else if (argc == 4) {
701 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
702 (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
703 } else {
704 result = ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc-3,
705 argv+3, TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
706 }
707 Tk_Release((ClientData) mePtr);
708 } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
709 && (length >= 3)) {
710 int index;
711
712 if (argc != 3) {
713 Tcl_AppendResult(interp, "wrong # args: should be \"",
714 argv[0], " index string\"", (char *) NULL);
715 goto error;
716 }
717 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
718 goto error;
719 }
720 if (index < 0) {
721 interp->result = "none";
722 } else {
723 sprintf(interp->result, "%d", index);
724 }
725 } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0)
726 && (length >= 3)) {
727 int index;
728
729 if (argc != 3) {
730 Tcl_AppendResult(interp, "wrong # args: should be \"",
731 argv[0], " invoke index\"", (char *) NULL);
732 goto error;
733 }
734 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
735 goto error;
736 }
737 if (index < 0) {
738 goto done;
739 }
740 mePtr = menuPtr->entries[index];
741 if (mePtr->state == tkDisabledUid) {
742 goto done;
743 }
744 Tk_Preserve((ClientData) mePtr);
745 if (mePtr->type == CHECK_BUTTON_ENTRY) {
746 if (mePtr->flags & ENTRY_SELECTED) {
747 Tcl_SetVar(interp, mePtr->name, mePtr->offValue,
748 TCL_GLOBAL_ONLY);
749 } else {
750 Tcl_SetVar(interp, mePtr->name, mePtr->onValue,
751 TCL_GLOBAL_ONLY);
752 }
753 } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
754 Tcl_SetVar(interp, mePtr->name, mePtr->onValue, TCL_GLOBAL_ONLY);
755 }
756 if (mePtr->command != NULL) {
757 result = Tcl_GlobalEval(interp, mePtr->command);
758 }
759 Tk_Release((ClientData) mePtr);
760 } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0)) {
761 int x, y, tmp;
762 Tk_Uid group;
763
764 if ((argc != 4) && (argc != 5)) {
765 Tcl_AppendResult(interp, "wrong # args: should be \"",
766 argv[0], " post x y ?group?\"", (char *) NULL);
767 goto error;
768 }
769 if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK)
770 || (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) {
771 goto error;
772 }
773 if (argc == 5) {
774 group = Tk_GetUid(argv[4]);
775 } else {
776 group = Tk_GetUid("default");
777 }
778
779 /*
780 * Adjust the position of the menu if necessary to keep it
781 * on-screen.
782 */
783
784 tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin))
785 - Tk_Width(menuPtr->tkwin);
786 if (x > tmp) {
787 x = tmp;
788 }
789 if (x < 0) {
790 x = 0;
791 }
792 tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin))
793 - Tk_Height(menuPtr->tkwin);
794 if (y > tmp) {
795 y = tmp;
796 }
797 if (y < 0) {
798 y = 0;
799 }
800 if ((x != Tk_X(menuPtr->tkwin)) || (y != Tk_Y(menuPtr->tkwin))) {
801 Tk_MoveWindow(menuPtr->tkwin, x, y);
802 }
803 if (Tk_IsMapped(menuPtr->tkwin)) {
804 if (group != menuPtr->group) {
805 Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
806 Tk_ShareEvents(menuPtr->tkwin, group);
807 }
808 } else {
809 Tk_ShareEvents(menuPtr->tkwin, group);
810 Tk_MapWindow(menuPtr->tkwin);
811 result = ActivateMenuEntry(menuPtr, -1);
812 }
813 XRaiseWindow(Tk_Display(menuPtr->tkwin), Tk_WindowId(menuPtr->tkwin));
814 menuPtr->group = group;
815 } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) {
816 if (argc != 2) {
817 Tcl_AppendResult(interp, "wrong # args: should be \"",
818 argv[0], " unpost\"", (char *) NULL);
819 goto error;
820 }
821 Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
822 Tk_UnmapWindow(menuPtr->tkwin);
823 result = ActivateMenuEntry(menuPtr, -1);
824 if (result == TCL_OK) {
825 result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
826 }
827 } else {
828 Tcl_AppendResult(interp, "bad option \"", argv[1],
829 "\": must be activate, add, configure, delete, disable, ",
830 "enable, entryconfigure, index, invoke, post, ",
831 "or unpost", (char *) NULL);
832 goto error;
833 }
834 done:
835 Tk_Release((ClientData) menuPtr);
836 return result;
837
838 error:
839 Tk_Release((ClientData) menuPtr);
840 return TCL_ERROR;
841 }
842 \f
843 /*
844 *----------------------------------------------------------------------
845 *
846 * DestroyMenu --
847 *
848 * This procedure is invoked by Tk_EventuallyFree or Tk_Release
849 * to clean up the internal structure of a menu at a safe time
850 * (when no-one is using it anymore).
851 *
852 * Results:
853 * None.
854 *
855 * Side effects:
856 * Everything associated with the menu is freed up.
857 *
858 *----------------------------------------------------------------------
859 */
860
861 static void
862 DestroyMenu(clientData)
863 ClientData clientData; /* Info about menu widget. */
864 {
865 register Menu *menuPtr = (Menu *) clientData;
866 int i;
867
868 for (i = 0; i < menuPtr->numEntries; i++) {
869 DestroyMenuEntry((ClientData) menuPtr->entries[i]);
870 }
871 if (menuPtr->entries != NULL) {
872 ckfree((char *) menuPtr->entries);
873 }
874 if (menuPtr->border != NULL) {
875 Tk_Free3DBorder(menuPtr->border);
876 }
877 if (menuPtr->activeBorder != NULL) {
878 Tk_Free3DBorder(menuPtr->activeBorder);
879 }
880 if (menuPtr->fontPtr != NULL) {
881 Tk_FreeFontStruct(menuPtr->fontPtr);
882 }
883 if (menuPtr->fg != NULL) {
884 Tk_FreeColor(menuPtr->fg);
885 }
886 if (menuPtr->textGC != None) {
887 Tk_FreeGC(menuPtr->textGC);
888 }
889 if (menuPtr->disabledFg != NULL) {
890 Tk_FreeColor(menuPtr->disabledFg);
891 }
892 if (menuPtr->gray != None) {
893 Tk_FreeBitmap(menuPtr->gray);
894 }
895 if (menuPtr->disabledGC != None) {
896 Tk_FreeGC(menuPtr->disabledGC);
897 }
898 if (menuPtr->activeFg != NULL) {
899 Tk_FreeColor(menuPtr->activeFg);
900 }
901 if (menuPtr->activeGC != None) {
902 Tk_FreeGC(menuPtr->activeGC);
903 }
904 if (menuPtr->selectorFg != NULL) {
905 Tk_FreeColor(menuPtr->selectorFg);
906 }
907 if (menuPtr->selectorGC != None) {
908 Tk_FreeGC(menuPtr->selectorGC);
909 }
910 if (menuPtr->cursor != None) {
911 Tk_FreeCursor(menuPtr->cursor);
912 }
913 ckfree((char *) menuPtr);
914 }
915 \f
916 /*
917 *----------------------------------------------------------------------
918 *
919 * DestroyMenuEntry --
920 *
921 * This procedure is invoked by Tk_EventuallyFree or Tk_Release
922 * to clean up the internal structure of a menu entry at a safe time
923 * (when no-one is using it anymore).
924 *
925 * Results:
926 * None.
927 *
928 * Side effects:
929 * Everything associated with the menu entry is freed up.
930 *
931 *----------------------------------------------------------------------
932 */
933
934 static void
935 DestroyMenuEntry(clientData)
936 ClientData clientData; /* Pointer to entry to be freed. */
937 {
938 register MenuEntry *mePtr = (MenuEntry *) clientData;
939 Menu *menuPtr = mePtr->menuPtr;
940
941 if (mePtr->name != NULL) {
942 Tcl_UntraceVar(menuPtr->interp, mePtr->name,
943 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
944 MenuVarProc, (ClientData) mePtr);
945 }
946 if (menuPtr->postedCascade == mePtr) {
947 if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL)
948 != TCL_OK) {
949 TkBindError(menuPtr->interp);
950 }
951 }
952 if (mePtr->label != NULL) {
953 ckfree(mePtr->label);
954 }
955 if (mePtr->bitmap != None) {
956 #if defined(USE_XPM3)
957 Tk_FreePixmap(mePtr->bitmap);
958 #else
959 Tk_FreeBitmap(mePtr->bitmap);
960 #endif
961 }
962 if (mePtr->accel != NULL) {
963 ckfree(mePtr->accel);
964 }
965 if (mePtr->border != NULL) {
966 Tk_Free3DBorder(mePtr->border);
967 }
968 if (mePtr->activeBorder != NULL) {
969 Tk_Free3DBorder(mePtr->activeBorder);
970 }
971 if (mePtr->fontPtr != NULL) {
972 Tk_FreeFontStruct(mePtr->fontPtr);
973 }
974 if (mePtr->textGC != NULL) {
975 Tk_FreeGC(mePtr->textGC);
976 }
977 if (mePtr->activeGC != NULL) {
978 Tk_FreeGC(mePtr->activeGC);
979 }
980 if (mePtr->disabledGC != NULL) {
981 Tk_FreeGC(mePtr->disabledGC);
982 }
983 if (mePtr->command != NULL) {
984 ckfree(mePtr->command);
985 }
986 if (mePtr->name != NULL) {
987 ckfree(mePtr->name);
988 }
989 if (mePtr->onValue != NULL) {
990 ckfree(mePtr->onValue);
991 }
992 if (mePtr->offValue != NULL) {
993 ckfree(mePtr->offValue);
994 }
995 ckfree((char *) mePtr);
996 }
997 \f
998 /*
999 *----------------------------------------------------------------------
1000 *
1001 * ConfigureMenu --
1002 *
1003 * This procedure is called to process an argv/argc list, plus
1004 * the Tk option database, in order to configure (or
1005 * reconfigure) a menu widget.
1006 *
1007 * Results:
1008 * The return value is a standard Tcl result. If TCL_ERROR is
1009 * returned, then interp->result contains an error message.
1010 *
1011 * Side effects:
1012 * Configuration information, such as colors, font, etc. get set
1013 * for menuPtr; old resources get freed, if there were any.
1014 *
1015 *----------------------------------------------------------------------
1016 */
1017
1018 static int
1019 ConfigureMenu(interp, menuPtr, argc, argv, flags)
1020 Tcl_Interp *interp; /* Used for error reporting. */
1021 register Menu *menuPtr; /* Information about widget; may or may
1022 * not already have values for some fields. */
1023 int argc; /* Number of valid entries in argv. */
1024 char **argv; /* Arguments. */
1025 int flags; /* Flags to pass to Tk_ConfigureWidget. */
1026 {
1027 XGCValues gcValues;
1028 GC newGC;
1029 unsigned long mask;
1030 int i;
1031
1032 if (Tk_ConfigureWidget(interp, menuPtr->tkwin, configSpecs,
1033 argc, argv, (char *) menuPtr, flags) != TCL_OK) {
1034 return TCL_ERROR;
1035 }
1036
1037 /*
1038 * A few options need special processing, such as setting the
1039 * background from a 3-D border, or filling in complicated
1040 * defaults that couldn't be specified to Tk_ConfigureWidget.
1041 */
1042
1043 Tk_SetBackgroundFromBorder(menuPtr->tkwin, menuPtr->border);
1044
1045 gcValues.font = menuPtr->fontPtr->fid;
1046 gcValues.foreground = menuPtr->fg->pixel;
1047 gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel;
1048 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
1049 &gcValues);
1050 if (menuPtr->textGC != None) {
1051 Tk_FreeGC(menuPtr->textGC);
1052 }
1053 menuPtr->textGC = newGC;
1054
1055 if (menuPtr->disabledFg != NULL) {
1056 gcValues.foreground = menuPtr->disabledFg->pixel;
1057 mask = GCForeground|GCBackground|GCFont;
1058 } else {
1059 gcValues.foreground = gcValues.background;
1060 if (menuPtr->gray == None) {
1061 menuPtr->gray = Tk_GetBitmap(interp, menuPtr->tkwin,
1062 Tk_GetUid("gray50"));
1063 if (menuPtr->gray == None) {
1064 return TCL_ERROR;
1065 }
1066 }
1067 gcValues.fill_style = FillStippled;
1068 gcValues.stipple = menuPtr->gray;
1069 mask = GCForeground|GCFillStyle|GCStipple;
1070 }
1071 newGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
1072 if (menuPtr->disabledGC != None) {
1073 Tk_FreeGC(menuPtr->disabledGC);
1074 }
1075 menuPtr->disabledGC = newGC;
1076
1077 gcValues.font = menuPtr->fontPtr->fid;
1078 gcValues.foreground = menuPtr->activeFg->pixel;
1079 gcValues.background = Tk_3DBorderColor(menuPtr->activeBorder)->pixel;
1080 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
1081 &gcValues);
1082 if (menuPtr->activeGC != None) {
1083 Tk_FreeGC(menuPtr->activeGC);
1084 }
1085 menuPtr->activeGC = newGC;
1086
1087 gcValues.foreground = menuPtr->selectorFg->pixel;
1088 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCFont, &gcValues);
1089 if (menuPtr->selectorGC != None) {
1090 Tk_FreeGC(menuPtr->selectorGC);
1091 }
1092 menuPtr->selectorGC = newGC;
1093
1094 /*
1095 * After reconfiguring a menu, we need to reconfigure all of the
1096 * entries in the menu, since some of the things in the children
1097 * (such as graphics contexts) may have to change to reflect changes
1098 * in the parent.
1099 */
1100
1101 for (i = 0; i < menuPtr->numEntries; i++) {
1102 MenuEntry *mePtr;
1103
1104 mePtr = menuPtr->entries[i];
1105 ConfigureMenuEntry(interp, menuPtr, mePtr, i, 0, (char **) NULL,
1106 TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
1107 }
1108
1109 if (!(menuPtr->flags & RESIZE_PENDING)) {
1110 menuPtr->flags |= RESIZE_PENDING;
1111 Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
1112 }
1113
1114 return TCL_OK;
1115 }
1116 \f
1117 /*
1118 *----------------------------------------------------------------------
1119 *
1120 * ConfigureMenuEntry --
1121 *
1122 * This procedure is called to process an argv/argc list, plus
1123 * the Tk option database, in order to configure (or
1124 * reconfigure) one entry in a menu.
1125 *
1126 * Results:
1127 * The return value is a standard Tcl result. If TCL_ERROR is
1128 * returned, then interp->result contains an error message.
1129 *
1130 * Side effects:
1131 * Configuration information such as label and accelerator get
1132 * set for mePtr; old resources get freed, if there were any.
1133 *
1134 *----------------------------------------------------------------------
1135 */
1136
1137 static int
1138 ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc, argv, flags)
1139 Tcl_Interp *interp; /* Used for error reporting. */
1140 Menu *menuPtr; /* Information about whole menu. */
1141 register MenuEntry *mePtr; /* Information about menu entry; may
1142 * or may not already have values for
1143 * some fields. */
1144 int index; /* Index of mePtr within menuPtr's
1145 * entries. */
1146 int argc; /* Number of valid entries in argv. */
1147 char **argv; /* Arguments. */
1148 int flags; /* Additional flags to pass to
1149 * Tk_ConfigureWidget. */
1150 {
1151 XGCValues gcValues;
1152 GC newGC, newActiveGC, newDisabledGC;
1153 unsigned long mask;
1154
1155 /*
1156 * If this entry is a cascade and the cascade is posted, then unpost
1157 * it before reconfiguring the entry (otherwise the reconfigure might
1158 * change the name of the cascaded entry, leaving a posted menu
1159 * high and dry).
1160 */
1161
1162 if (menuPtr->postedCascade == mePtr) {
1163 if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL)
1164 != TCL_OK) {
1165 TkBindError(menuPtr->interp);
1166 }
1167 }
1168
1169 /*
1170 * If this entry is a check button or radio button, then remove
1171 * its old trace procedure.
1172 */
1173
1174 if ((mePtr->name != NULL) &&
1175 ((mePtr->type == CHECK_BUTTON_ENTRY)
1176 || (mePtr->type == RADIO_BUTTON_ENTRY))) {
1177 Tcl_UntraceVar(menuPtr->interp, mePtr->name,
1178 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1179 MenuVarProc, (ClientData) mePtr);
1180 }
1181
1182 if (Tk_ConfigureWidget(interp, menuPtr->tkwin, entryConfigSpecs,
1183 argc, argv, (char *) mePtr,
1184 flags | (COMMAND_MASK << mePtr->type)) != TCL_OK) {
1185 return TCL_ERROR;
1186 }
1187
1188 /*
1189 * The code below handles special configuration stuff not taken
1190 * care of by Tk_ConfigureWidget, such as special processing for
1191 * defaults, sizing strings, graphics contexts, etc.
1192 */
1193
1194 if (mePtr->label == NULL) {
1195 mePtr->labelLength = 0;
1196 } else {
1197 mePtr->labelLength = strlen(mePtr->label);
1198 }
1199 if (mePtr->accel == NULL) {
1200 mePtr->accelLength = 0;
1201 } else {
1202 mePtr->accelLength = strlen(mePtr->accel);
1203 }
1204
1205 if (mePtr->state == tkActiveUid) {
1206 if (index != menuPtr->active) {
1207 ActivateMenuEntry(menuPtr, index);
1208 }
1209 } else {
1210 if (index == menuPtr->active) {
1211 ActivateMenuEntry(menuPtr, -1);
1212 }
1213 if ((mePtr->state != tkNormalUid) && (mePtr->state != tkDisabledUid)) {
1214 Tcl_AppendResult(interp, "bad state value \"", mePtr->state,
1215 "\": must be normal, active, or disabled", (char *) NULL);
1216 mePtr->state = tkNormalUid;
1217 return TCL_ERROR;
1218 }
1219 }
1220
1221 if (mePtr->fontPtr != NULL) {
1222 gcValues.foreground = menuPtr->fg->pixel;
1223 gcValues.background = Tk_3DBorderColor(
1224 (mePtr->border != NULL) ? mePtr->border : menuPtr->border)
1225 ->pixel;
1226 gcValues.font = mePtr->fontPtr->fid;
1227
1228 /*
1229 * Note: disable GraphicsExpose events; we know there won't be
1230 * obscured areas when copying from an off-screen pixmap to the
1231 * screen and this gets rid of unnecessary events.
1232 */
1233
1234 gcValues.graphics_exposures = False;
1235 newGC = Tk_GetGC(menuPtr->tkwin,
1236 GCForeground|GCBackground|GCFont|GCGraphicsExposures,
1237 &gcValues);
1238
1239 if (menuPtr->disabledFg != NULL) {
1240 gcValues.foreground = menuPtr->disabledFg->pixel;
1241 mask = GCForeground|GCBackground|GCFont|GCGraphicsExposures;
1242 } else {
1243 gcValues.foreground = gcValues.background;
1244 gcValues.fill_style = FillStippled;
1245 gcValues.stipple = menuPtr->gray;
1246 mask = GCForeground|GCFillStyle|GCStipple;
1247 }
1248 newDisabledGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
1249
1250 gcValues.foreground = menuPtr->activeFg->pixel;
1251 gcValues.background = Tk_3DBorderColor(
1252 (mePtr->activeBorder != NULL) ? mePtr->activeBorder
1253 : menuPtr->activeBorder)->pixel;
1254 newActiveGC = Tk_GetGC(menuPtr->tkwin,
1255 GCForeground|GCBackground|GCFont|GCGraphicsExposures,
1256 &gcValues);
1257 } else {
1258 newGC = NULL;
1259 newActiveGC = NULL;
1260 newDisabledGC = NULL;
1261 }
1262 if (mePtr->textGC != NULL) {
1263 Tk_FreeGC(mePtr->textGC);
1264 }
1265 mePtr->textGC = newGC;
1266 if (mePtr->activeGC != NULL) {
1267 Tk_FreeGC(mePtr->activeGC);
1268 }
1269 mePtr->activeGC = newActiveGC;
1270 if (mePtr->disabledGC != NULL) {
1271 Tk_FreeGC(mePtr->disabledGC);
1272 }
1273 mePtr->disabledGC = newDisabledGC;
1274
1275 if ((mePtr->type == CHECK_BUTTON_ENTRY)
1276 || (mePtr->type == RADIO_BUTTON_ENTRY)) {
1277 char *value;
1278
1279 if (mePtr->name == NULL) {
1280 mePtr->name = ckalloc((unsigned) (strlen(mePtr->label) + 1));
1281 strcpy(mePtr->name, mePtr->label);
1282 }
1283 if (mePtr->onValue == NULL) {
1284 mePtr->onValue = ckalloc((unsigned) (strlen(mePtr->label) + 1));
1285 strcpy(mePtr->onValue, mePtr->label);
1286 }
1287
1288 /*
1289 * Select the entry if the associated variable has the
1290 * appropriate value, initialize the variable if it doesn't
1291 * exist, then set a trace on the variable to monitor future
1292 * changes to its value.
1293 */
1294
1295 value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY);
1296 mePtr->flags &= ENTRY_SELECTED;
1297 if (value != NULL) {
1298 if (strcmp(value, mePtr->onValue) == 0) {
1299 mePtr->flags |= ENTRY_SELECTED;
1300 }
1301 } else {
1302 Tcl_SetVar(interp, mePtr->name,
1303 (mePtr->type == CHECK_BUTTON_ENTRY) ? mePtr->offValue : "",
1304 TCL_GLOBAL_ONLY);
1305 }
1306 Tcl_TraceVar(interp, mePtr->name,
1307 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1308 MenuVarProc, (ClientData) mePtr);
1309 }
1310
1311 if (!(menuPtr->flags & RESIZE_PENDING)) {
1312 menuPtr->flags |= RESIZE_PENDING;
1313 Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
1314 }
1315 return TCL_OK;
1316 }
1317 \f
1318 /*
1319 *--------------------------------------------------------------
1320 *
1321 * ComputeMenuGeometry --
1322 *
1323 * This procedure is invoked to recompute the size and
1324 * layout of a menu. It is called as a when-idle handler so
1325 * that it only gets done once, even if a group of changes is
1326 * made to the menu.
1327 *
1328 * Results:
1329 * None.
1330 *
1331 * Side effects:
1332 * Fields of menu entries are changed to reflect their
1333 * current positions, and the size of the menu window
1334 * itself may be changed.
1335 *
1336 *--------------------------------------------------------------
1337 */
1338
1339 static void
1340 ComputeMenuGeometry(clientData)
1341 ClientData clientData; /* Structure describing menu. */
1342 {
1343 Menu *menuPtr = (Menu *) clientData;
1344 register MenuEntry *mePtr;
1345 XFontStruct *fontPtr;
1346 int maxLabelWidth, maxSelectorWidth, maxAccelWidth;
1347 int width, height, selectorSpace, horizMargin;
1348 int i, y;
1349
1350 if (menuPtr->tkwin == NULL) {
1351 return;
1352 }
1353
1354 maxLabelWidth = maxSelectorWidth = maxAccelWidth = 0;
1355 y = menuPtr->borderWidth;
1356
1357 for (i = 0; i < menuPtr->numEntries; i++) {
1358 mePtr = menuPtr->entries[i];
1359 selectorSpace = 0;
1360 fontPtr = mePtr->fontPtr;
1361 if (fontPtr == NULL) {
1362 fontPtr = menuPtr->fontPtr;
1363 }
1364
1365 /*
1366 * For each entry, compute the height required by that
1367 * particular entry, plus three widths: the width of the
1368 * label, the width to allow for a selector to be displayed
1369 * to the left of the label (if any), and the width of the
1370 * accelerator to be displayed to the right of the label
1371 * (if any). These sizes depend, of course, on the type
1372 * of the entry.
1373 */
1374
1375 if (mePtr->bitmap != None) {
1376 unsigned int bitmapWidth, bitmapHeight;
1377
1378 #if defined(USE_XPM3)
1379 Tk_SizeOfPixmap(mePtr->bitmap, &bitmapWidth, &bitmapHeight);
1380 #else
1381 Tk_SizeOfBitmap(mePtr->bitmap, &bitmapWidth, &bitmapHeight);
1382 #endif
1383 mePtr->height = bitmapHeight;
1384 width = bitmapWidth;
1385 if (mePtr->type == CHECK_BUTTON_ENTRY) {
1386 selectorSpace = (14*mePtr->height)/10;
1387 mePtr->selectorDiameter = (65*mePtr->height)/100;
1388 } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
1389 selectorSpace = (14*mePtr->height)/10;
1390 mePtr->selectorDiameter = (75*mePtr->height)/100;
1391 }
1392 } else {
1393 mePtr->height = fontPtr->ascent + fontPtr->descent;
1394 if (mePtr->label != NULL) {
1395 (void) TkMeasureChars(fontPtr, mePtr->label,
1396 mePtr->labelLength, 0, (int) 100000,
1397 TK_NEWLINES_NOT_SPECIAL, &width);
1398 } else {
1399 width = 0;
1400 }
1401 if (mePtr->type == CHECK_BUTTON_ENTRY) {
1402 selectorSpace = mePtr->height;
1403 mePtr->selectorDiameter = (80*mePtr->height)/100;
1404 } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
1405 selectorSpace = mePtr->height;
1406 mePtr->selectorDiameter = mePtr->height;
1407 }
1408 }
1409 mePtr->height += 2*menuPtr->activeBorderWidth + 2;
1410 if (width > maxLabelWidth) {
1411 maxLabelWidth = width;
1412 }
1413 if (mePtr->accel != NULL) {
1414 (void) TkMeasureChars(fontPtr, mePtr->accel, mePtr->accelLength,
1415 0, (int) 100000, TK_NEWLINES_NOT_SPECIAL, &width);
1416 if (width > maxAccelWidth) {
1417 maxAccelWidth = width;
1418 }
1419 }
1420 if (mePtr->type == SEPARATOR_ENTRY) {
1421 mePtr->height = 4*menuPtr->borderWidth;
1422 }
1423 if (selectorSpace > maxSelectorWidth) {
1424 maxSelectorWidth = selectorSpace;
1425 }
1426 mePtr->y = y;
1427 y += mePtr->height;
1428 }
1429
1430 /*
1431 * Got all the sizes. Update fields in the menu structure, then
1432 * resize the window if necessary. Leave margins on either side
1433 * of the selector (or just one margin if there is no selector).
1434 * Leave another margin on the right side of the label, plus yet
1435 * another margin to the right of the accelerator (if there is one).
1436 */
1437
1438 horizMargin = 2;
1439 menuPtr->selectorSpace = maxSelectorWidth + horizMargin;
1440 if (maxSelectorWidth != 0) {
1441 menuPtr->selectorSpace += horizMargin;
1442 }
1443 menuPtr->labelWidth = maxLabelWidth + horizMargin;
1444 width = menuPtr->selectorSpace + menuPtr->labelWidth + maxAccelWidth
1445 + 2*menuPtr->borderWidth + 2*menuPtr->activeBorderWidth + 2;
1446 if (maxAccelWidth != 0) {
1447 width += horizMargin;
1448 }
1449 height = y + menuPtr->borderWidth;
1450
1451 /*
1452 * The X server doesn't like zero dimensions, so round up to at least
1453 * 1 (a zero-sized menu should never really occur, anyway).
1454 */
1455
1456 if (width <= 0) {
1457 width = 1;
1458 }
1459 if (height <= 0) {
1460 height = 1;
1461 }
1462 if ((width != Tk_ReqWidth(menuPtr->tkwin)) ||
1463 (height != Tk_ReqHeight(menuPtr->tkwin))) {
1464 Tk_GeometryRequest(menuPtr->tkwin, width, height);
1465 } else {
1466 /*
1467 * Must always force a redisplay here if the window is mapped
1468 * (even if the size didn't change, something else might have
1469 * changed in the menu, such as a label or accelerator). The
1470 * resize will force a redisplay above.
1471 */
1472
1473 EventuallyRedrawMenu(menuPtr, -1);
1474 }
1475
1476 menuPtr->flags &= ~RESIZE_PENDING;
1477 }
1478 \f
1479 /*
1480 *----------------------------------------------------------------------
1481 *
1482 * DisplayMenu --
1483 *
1484 * This procedure is invoked to display a menu widget.
1485 *
1486 * Results:
1487 * None.
1488 *
1489 * Side effects:
1490 * Commands are output to X to display the menu in its
1491 * current mode.
1492 *
1493 *----------------------------------------------------------------------
1494 */
1495
1496 static void
1497 DisplayMenu(clientData)
1498 ClientData clientData; /* Information about widget. */
1499 {
1500 register Menu *menuPtr = (Menu *) clientData;
1501 register MenuEntry *mePtr;
1502 register Tk_Window tkwin = menuPtr->tkwin;
1503 XFontStruct *fontPtr;
1504 int index, baseline;
1505 GC gc;
1506
1507 menuPtr->flags &= ~REDRAW_PENDING;
1508 if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
1509 return;
1510 }
1511
1512 /*
1513 * Loop through all of the entries, drawing them one at a time.
1514 */
1515
1516 for (index = 0; index < menuPtr->numEntries; index++) {
1517 mePtr = menuPtr->entries[index];
1518 if (!(mePtr->flags & ENTRY_NEEDS_REDISPLAY)) {
1519 continue;
1520 }
1521 mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY;
1522
1523 /*
1524 * Background.
1525 */
1526
1527 if (mePtr->state == tkActiveUid) {
1528 Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1529 (mePtr->activeBorder != NULL) ? mePtr->activeBorder
1530 : menuPtr->activeBorder, menuPtr->borderWidth, mePtr->y,
1531 Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
1532 menuPtr->activeBorderWidth, TK_RELIEF_RAISED);
1533 gc = mePtr->activeGC;
1534 if (gc == NULL) {
1535 gc = menuPtr->activeGC;
1536 }
1537 } else {
1538 Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1539 (mePtr->border != NULL) ? mePtr->border
1540 : menuPtr->border, menuPtr->borderWidth, mePtr->y,
1541 Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
1542 0, TK_RELIEF_FLAT);
1543 if ((mePtr->state == tkDisabledUid)
1544 && (menuPtr->disabledFg != NULL)) {
1545 gc = mePtr->disabledGC;
1546 if (gc == NULL) {
1547 gc = menuPtr->disabledGC;
1548 }
1549 } else {
1550 gc = mePtr->textGC;
1551 if (gc == NULL) {
1552 gc = menuPtr->textGC;
1553 }
1554 }
1555 }
1556
1557 /*
1558 * Draw label or bitmap for entry.
1559 */
1560
1561 fontPtr = mePtr->fontPtr;
1562 if (fontPtr == NULL) {
1563 fontPtr = menuPtr->fontPtr;
1564 }
1565 baseline = mePtr->y + (mePtr->height + fontPtr->ascent
1566 - fontPtr->descent)/2;
1567 if (mePtr->bitmap != None) {
1568 unsigned int width, height;
1569
1570 #if defined(USE_XPM3)
1571 Tk_SizeOfPixmap(mePtr->bitmap, &width, &height);
1572 XCopyArea(Tk_Display(tkwin), mePtr->bitmap, Tk_WindowId(tkwin),
1573 gc, 0, 0, width, height,
1574 menuPtr->borderWidth + menuPtr->selectorSpace,
1575 (int) (mePtr->y + (mePtr->height - height)/2));
1576 #else
1577 Tk_SizeOfBitmap(mePtr->bitmap, &width, &height);
1578 XCopyPlane(Tk_Display(tkwin), mePtr->bitmap, Tk_WindowId(tkwin),
1579 gc, 0, 0, width, height,
1580 menuPtr->borderWidth + menuPtr->selectorSpace,
1581 (int) (mePtr->y + (mePtr->height - height)/2), 1);
1582 #endif
1583 } else {
1584 baseline = mePtr->y + (mePtr->height + fontPtr->ascent
1585 - fontPtr->descent)/2;
1586 if (mePtr->label != NULL) {
1587 TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
1588 fontPtr, mePtr->label, mePtr->labelLength,
1589 menuPtr->borderWidth + menuPtr->selectorSpace,
1590 baseline, TK_NEWLINES_NOT_SPECIAL);
1591 if (mePtr->underline >= 0) {
1592 TkUnderlineChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
1593 fontPtr, mePtr->label,
1594 menuPtr->borderWidth + menuPtr->selectorSpace,
1595 baseline, TK_NEWLINES_NOT_SPECIAL,
1596 mePtr->underline, mePtr->underline);
1597 }
1598 }
1599 }
1600
1601 /*
1602 * Draw accelerator.
1603 */
1604
1605 if (mePtr->accel != NULL) {
1606 TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
1607 fontPtr, mePtr->accel, mePtr->accelLength,
1608 menuPtr->borderWidth + menuPtr->selectorSpace
1609 + menuPtr->labelWidth, baseline, TK_NEWLINES_NOT_SPECIAL);
1610 }
1611
1612 /*
1613 * Draw check-button selector.
1614 */
1615
1616 if (mePtr->type == CHECK_BUTTON_ENTRY) {
1617 int dim, x, y;
1618
1619 dim = mePtr->selectorDiameter;
1620 x = menuPtr->borderWidth + (menuPtr->selectorSpace - dim)/2;
1621 y = mePtr->y + (mePtr->height - dim)/2;
1622 Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1623 menuPtr->border, x, y, dim, dim,
1624 menuPtr->activeBorderWidth, TK_RELIEF_SUNKEN);
1625 x += menuPtr->activeBorderWidth;
1626 y += menuPtr->activeBorderWidth;
1627 dim -= 2*menuPtr->activeBorderWidth;
1628 if ((dim > 0) && (mePtr->flags & ENTRY_SELECTED)) {
1629 XFillRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1630 menuPtr->selectorGC, x, y, (unsigned int) dim,
1631 (unsigned int) dim);
1632 }
1633 }
1634
1635 /*
1636 * Draw radio-button selector.
1637 */
1638
1639 if (mePtr->type == RADIO_BUTTON_ENTRY) {
1640 XPoint points[4];
1641 int radius;
1642
1643 radius = mePtr->selectorDiameter/2;
1644 points[0].x = menuPtr->borderWidth
1645 + (menuPtr->selectorSpace - mePtr->selectorDiameter)/2;
1646 points[0].y = mePtr->y + (mePtr->height)/2;
1647 points[1].x = points[0].x + radius;
1648 points[1].y = points[0].y + radius;
1649 points[2].x = points[1].x + radius;
1650 points[2].y = points[0].y;
1651 points[3].x = points[1].x;
1652 points[3].y = points[0].y - radius;
1653 if (mePtr->flags & ENTRY_SELECTED) {
1654 XFillPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
1655 menuPtr->selectorGC, points, 4, Convex,
1656 CoordModeOrigin);
1657 } else {
1658 Tk_Fill3DPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
1659 menuPtr->border, points, 4, menuPtr->activeBorderWidth,
1660 TK_RELIEF_FLAT);
1661 }
1662 Tk_Draw3DPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
1663 menuPtr->border, points, 4, menuPtr->activeBorderWidth,
1664 TK_RELIEF_SUNKEN);
1665 }
1666
1667 /*
1668 * Draw separator.
1669 */
1670
1671 if (mePtr->type == SEPARATOR_ENTRY) {
1672 XPoint points[2];
1673 int margin;
1674
1675 margin = (fontPtr->ascent + fontPtr->descent)/2;
1676 points[0].x = 2*menuPtr->borderWidth + margin;
1677 points[0].y = mePtr->y + mePtr->height/2;
1678 points[1].x = Tk_Width(tkwin) - 2*menuPtr->borderWidth - margin;
1679 points[1].y = points[0].y;
1680 Tk_Draw3DPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
1681 menuPtr->border, points, 2, 1, TK_RELIEF_RAISED);
1682 }
1683
1684 /*
1685 * If the entry is disabled with a stipple rather than a special
1686 * foreground color, generate the stippled effect.
1687 */
1688
1689 if ((mePtr->state == tkDisabledUid) && (menuPtr->disabledFg == NULL)) {
1690 XFillRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1691 menuPtr->disabledGC, menuPtr->borderWidth,
1692 mePtr->y,
1693 (unsigned) (Tk_Width(tkwin) - 2*menuPtr->borderWidth),
1694 (unsigned) mePtr->height);
1695 }
1696 }
1697
1698 Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1699 menuPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
1700 menuPtr->borderWidth, TK_RELIEF_RAISED);
1701 }
1702 \f
1703 /*
1704 *--------------------------------------------------------------
1705 *
1706 * GetMenuIndex --
1707 *
1708 * Parse a textual index into a menu and return the numerical
1709 * index of the indicated entry.
1710 *
1711 * Results:
1712 * A standard Tcl result. If all went well, then *indexPtr is
1713 * filled in with the entry index corresponding to string
1714 * (ranges from -1 to the number of entries in the menu minus
1715 * one). Otherwise an error message is left in interp->result.
1716 *
1717 * Side effects:
1718 * None.
1719 *
1720 *--------------------------------------------------------------
1721 */
1722
1723 static int
1724 GetMenuIndex(interp, menuPtr, string, indexPtr)
1725 Tcl_Interp *interp; /* For error messages. */
1726 Menu *menuPtr; /* Menu for which the index is being
1727 * specified. */
1728 char *string; /* Specification of an entry in menu. See
1729 * manual entry for valid .*/
1730 int *indexPtr; /* Where to store converted relief. */
1731 {
1732 int i, y;
1733
1734 if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
1735 *indexPtr = menuPtr->active;
1736 return TCL_OK;
1737 }
1738
1739 if ((string[0] == 'l') && (strcmp(string, "last") == 0)) {
1740 *indexPtr = menuPtr->numEntries-1;
1741 return TCL_OK;
1742 }
1743
1744 if ((string[0] == 'n') && (strcmp(string, "none") == 0)) {
1745 *indexPtr = -1;
1746 return TCL_OK;
1747 }
1748
1749 if (string[0] == '@') {
1750 if (Tcl_GetInt(interp, string+1, &y) == TCL_OK) {
1751 if (y < 0) {
1752 *indexPtr = -1;
1753 return TCL_OK;
1754 }
1755 for (i = 0; i < menuPtr->numEntries; i++) {
1756 y -= menuPtr->entries[i]->height;
1757 if (y < 0) {
1758 break;
1759 }
1760 }
1761 if (i >= menuPtr->numEntries) {
1762 i = -1;
1763 }
1764 *indexPtr = i;
1765 return TCL_OK;
1766 } else {
1767 Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
1768 }
1769 }
1770
1771 if (isdigit(string[0])) {
1772 if (Tcl_GetInt(interp, string, &i) == TCL_OK) {
1773 if ((i < menuPtr->numEntries) && (i >= 0)) {
1774 *indexPtr = i;
1775 return TCL_OK;
1776 }
1777 } else {
1778 Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
1779 }
1780 }
1781
1782 for (i = 0; i < menuPtr->numEntries; i++) {
1783 char *label;
1784
1785 label = menuPtr->entries[i]->label;
1786 if ((label != NULL)
1787 && (Tcl_StringMatch(menuPtr->entries[i]->label, string))) {
1788 *indexPtr = i;
1789 return TCL_OK;
1790 }
1791 }
1792
1793 Tcl_AppendResult(interp, "bad menu entry index \"",
1794 string, "\"", (char *) NULL);
1795 return TCL_ERROR;
1796 }
1797 \f
1798 /*
1799 *--------------------------------------------------------------
1800 *
1801 * MenuEventProc --
1802 *
1803 * This procedure is invoked by the Tk dispatcher for various
1804 * events on menus.
1805 *
1806 * Results:
1807 * None.
1808 *
1809 * Side effects:
1810 * When the window gets deleted, internal structures get
1811 * cleaned up. When it gets exposed, it is redisplayed.
1812 *
1813 *--------------------------------------------------------------
1814 */
1815
1816 static void
1817 MenuEventProc(clientData, eventPtr)
1818 ClientData clientData; /* Information about window. */
1819 XEvent *eventPtr; /* Information about event. */
1820 {
1821 Menu *menuPtr = (Menu *) clientData;
1822 if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
1823 EventuallyRedrawMenu(menuPtr, -1);
1824 } else if (eventPtr->type == DestroyNotify) {
1825 Tcl_DeleteCommand(menuPtr->interp, Tk_PathName(menuPtr->tkwin));
1826
1827 /*
1828 * Careful! Must delete the event-sharing information here
1829 * rather than in DestroyMenu. By the time that procedure
1830 * is called the tkwin may have been reused, resulting in some
1831 * other window accidentally being cut off from shared events.
1832 */
1833
1834 Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
1835 menuPtr->tkwin = NULL;
1836 if (menuPtr->flags & REDRAW_PENDING) {
1837 Tk_CancelIdleCall(DisplayMenu, (ClientData) menuPtr);
1838 }
1839 if (menuPtr->flags & RESIZE_PENDING) {
1840 Tk_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
1841 }
1842 Tk_EventuallyFree((ClientData) menuPtr, DestroyMenu);
1843 }
1844 }
1845 \f
1846 /*
1847 *--------------------------------------------------------------
1848 *
1849 * MenuVarProc --
1850 *
1851 * This procedure is invoked when someone changes the
1852 * state variable associated with a radiobutton or checkbutton
1853 * menu entry. The entry's selected state is set to match
1854 * the value of the variable.
1855 *
1856 * Results:
1857 * NULL is always returned.
1858 *
1859 * Side effects:
1860 * The menu entry may become selected or deselected.
1861 *
1862 *--------------------------------------------------------------
1863 */
1864
1865 /* ARGSUSED */
1866 static char *
1867 MenuVarProc(clientData, interp, name1, name2, flags)
1868 ClientData clientData; /* Information about menu entry. */
1869 Tcl_Interp *interp; /* Interpreter containing variable. */
1870 char *name1; /* First part of variable's name. */
1871 char *name2; /* Second part of variable's name. */
1872 int flags; /* Describes what just happened. */
1873 {
1874 MenuEntry *mePtr = (MenuEntry *) clientData;
1875 Menu *menuPtr;
1876 char *value;
1877
1878 menuPtr = mePtr->menuPtr;
1879
1880 /*
1881 * If the variable is being unset, then re-establish the
1882 * trace unless the whole interpreter is going away.
1883 */
1884
1885 if (flags & TCL_TRACE_UNSETS) {
1886 mePtr->flags &= ~ENTRY_SELECTED;
1887 if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
1888 Tcl_TraceVar2(interp, name1, name2,
1889 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1890 MenuVarProc, clientData);
1891 }
1892 EventuallyRedrawMenu(menuPtr, -1);
1893 return (char *) NULL;
1894 }
1895
1896 /*
1897 * Use the value of the variable to update the selected status of
1898 * the menu entry.
1899 */
1900
1901 value = Tcl_GetVar2(interp, name1, name2, flags & TCL_GLOBAL_ONLY);
1902 if (strcmp(value, mePtr->onValue) == 0) {
1903 if (mePtr->flags & ENTRY_SELECTED) {
1904 return (char *) NULL;
1905 }
1906 mePtr->flags |= ENTRY_SELECTED;
1907 } else if (mePtr->flags & ENTRY_SELECTED) {
1908 mePtr->flags &= ~ENTRY_SELECTED;
1909 } else {
1910 return (char *) NULL;
1911 }
1912 EventuallyRedrawMenu(menuPtr, -1);
1913 return (char *) NULL;
1914 }
1915 \f
1916 /*
1917 *----------------------------------------------------------------------
1918 *
1919 * EventuallyRedrawMenu --
1920 *
1921 * Arrange for an entry of a menu, or the whole menu, to be
1922 * redisplayed at some point in the future.
1923 *
1924 * Results:
1925 * None.
1926 *
1927 * Side effects:
1928 * A when-idle hander is scheduled to do the redisplay, if there
1929 * isn't one already scheduled.
1930 *
1931 *----------------------------------------------------------------------
1932 */
1933
1934 static void
1935 EventuallyRedrawMenu(menuPtr, index)
1936 register Menu *menuPtr; /* Information about menu to redraw. */
1937 int index; /* Which entry to redraw. If -1, then
1938 * all the entries in the menu are redrawn. */
1939 {
1940 if (menuPtr->tkwin == NULL) {
1941 return;
1942 }
1943 if (index != -1) {
1944 menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
1945 } else {
1946 for (index = 0; index < menuPtr->numEntries; index++) {
1947 menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
1948 }
1949 }
1950 if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)
1951 || (menuPtr->flags & REDRAW_PENDING)) {
1952 return;
1953 }
1954 Tk_DoWhenIdle(DisplayMenu, (ClientData) menuPtr);
1955 menuPtr->flags |= REDRAW_PENDING;
1956 }
1957 \f
1958 /*
1959 *--------------------------------------------------------------
1960 *
1961 * PostSubmenu --
1962 *
1963 * This procedure arranges for a particular submenu (i.e. the
1964 * menu corresponding to a given cascade entry) to be
1965 * posted.
1966 *
1967 * Results:
1968 * A standard Tcl return result. Errors may occur in the
1969 * Tcl commands generated to post and unpost submenus.
1970 *
1971 * Side effects:
1972 * If there is already a submenu posted, it is unposted.
1973 * The new submenu is then posted.
1974 *
1975 *--------------------------------------------------------------
1976 */
1977
1978 static int
1979 PostSubmenu(interp, menuPtr, mePtr)
1980 Tcl_Interp *interp; /* Used for invoking sub-commands and
1981 * reporting errors. */
1982 register Menu *menuPtr; /* Information about menu as a whole. */
1983 register MenuEntry *mePtr; /* Info about submenu that is to be
1984 * posted. NULL means make sure that
1985 * no submenu is posted. */
1986 {
1987 char string[30];
1988 int result, x, y;
1989
1990 if (mePtr == menuPtr->postedCascade) {
1991 return TCL_OK;
1992 }
1993
1994 if (menuPtr->postedCascade != NULL) {
1995 result = Tcl_VarEval(interp, menuPtr->postedCascade->name,
1996 " unpost", (char *) NULL);
1997 menuPtr->postedCascade = NULL;
1998 if (result != TCL_OK) {
1999 return result;
2000 }
2001 }
2002
2003 if ((mePtr != NULL) && (mePtr->name != NULL)) {
2004 Tk_GetRootCoords(menuPtr->tkwin, &x, &y);
2005 x += Tk_Width(menuPtr->tkwin);
2006 y += mePtr->y;
2007 sprintf(string, "%d %d ", x, y);
2008 result = Tcl_VarEval(interp, mePtr->name, " post ", string,
2009 menuPtr->group, (char *) NULL);
2010 if (result != TCL_OK) {
2011 return result;
2012 }
2013 menuPtr->postedCascade = mePtr;
2014 }
2015 return TCL_OK;
2016 }
2017 \f
2018 /*
2019 *----------------------------------------------------------------------
2020 *
2021 * ActivateMenuEntry --
2022 *
2023 * This procedure is invoked to make a particular menu entry
2024 * the active one, deactivating any other entry that might
2025 * currently be active.
2026 *
2027 * Results:
2028 * The return value is a standard Tcl result (errors can occur
2029 * while posting and unposting submenus).
2030 *
2031 * Side effects:
2032 * Menu entries get redisplayed, and the active entry changes.
2033 * Submenus may get posted and unposted.
2034 *
2035 *----------------------------------------------------------------------
2036 */
2037
2038 static int
2039 ActivateMenuEntry(menuPtr, index)
2040 register Menu *menuPtr; /* Menu in which to activate. */
2041 int index; /* Index of entry to activate, or
2042 * -1 to deactivate all entries. */
2043 {
2044 register MenuEntry *mePtr;
2045 int result = TCL_OK;
2046
2047 if (menuPtr->active >= 0) {
2048 mePtr = menuPtr->entries[menuPtr->active];
2049
2050 /*
2051 * Don't change the state unless it's currently active (state
2052 * might already have been changed to disabled).
2053 */
2054
2055 if (mePtr->state == tkActiveUid) {
2056 mePtr->state = tkNormalUid;
2057 }
2058 EventuallyRedrawMenu(menuPtr, menuPtr->active);
2059 }
2060 menuPtr->active = index;
2061 if (index >= 0) {
2062 mePtr = menuPtr->entries[index];
2063 mePtr->state = tkActiveUid;
2064 EventuallyRedrawMenu(menuPtr, index);
2065 Tk_Preserve((ClientData) mePtr);
2066 if (mePtr->type == CASCADE_ENTRY) {
2067 result = PostSubmenu(menuPtr->interp, menuPtr, mePtr);
2068 } else {
2069 result = PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL);
2070 }
2071 Tk_Release((ClientData) mePtr);
2072 }
2073 return result;
2074 }
Impressum, Datenschutz