]> cvs.zerfleddert.de Git - micropolis/blob - src/tk/tkmenu.c
Fixes for compilation with gcc 15
[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 (
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 {
392 Tk_Window tkwin = (Tk_Window) clientData;
393 Tk_Window new;
394 register Menu *menuPtr;
395 XSetWindowAttributes atts;
396
397 if (argc < 2) {
398 Tcl_AppendResult(interp, "wrong # args: should be \"",
399 argv[0], " pathName ?options?\"", (char *) NULL);
400 return TCL_ERROR;
401 }
402
403 /*
404 * Create the new window. Set override-redirect so the window
405 * manager won't add a border or argue about placement, and set
406 * save-under so that the window can pop up and down without a
407 * lot of re-drawing.
408 */
409
410 new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], "");
411 if (new == NULL) {
412 return TCL_ERROR;
413 }
414 atts.override_redirect = True;
415 atts.save_under = True;
416 Tk_ChangeWindowAttributes(new, CWOverrideRedirect|CWSaveUnder, &atts);
417
418 /*
419 * Initialize the data structure for the menu.
420 */
421
422 menuPtr = (Menu *) ckalloc(sizeof(Menu));
423 menuPtr->tkwin = new;
424 menuPtr->interp = interp;
425 menuPtr->entries = NULL;
426 menuPtr->numEntries = 0;
427 menuPtr->active = -1;
428 menuPtr->group = NULL;
429 menuPtr->border = NULL;
430 menuPtr->activeBorder = NULL;
431 menuPtr->fontPtr = NULL;
432 menuPtr->fg = NULL;
433 menuPtr->textGC = None;
434 menuPtr->disabledFg = NULL;
435 menuPtr->gray = None;
436 menuPtr->disabledGC = None;
437 menuPtr->activeFg = NULL;
438 menuPtr->activeGC = None;
439 menuPtr->selectorFg = NULL;
440 menuPtr->selectorGC = None;
441 menuPtr->cursor = None;
442 menuPtr->postedCascade = NULL;
443 menuPtr->flags = 0;
444
445 Tk_SetClass(new, "Menu");
446 Tk_CreateEventHandler(menuPtr->tkwin, ExposureMask|StructureNotifyMask,
447 MenuEventProc, (ClientData) menuPtr);
448 Tcl_CreateCommand(interp, Tk_PathName(menuPtr->tkwin), MenuWidgetCmd,
449 (ClientData) menuPtr, (void (*)(int *)) NULL);
450 if (ConfigureMenu(interp, menuPtr, argc-2, argv+2, 0) != TCL_OK) {
451 goto error;
452 }
453
454 interp->result = Tk_PathName(menuPtr->tkwin);
455 return TCL_OK;
456
457 error:
458 Tk_DestroyWindow(menuPtr->tkwin);
459 return TCL_ERROR;
460 }
461 \f
462 /*
463 *--------------------------------------------------------------
464 *
465 * MenuWidgetCmd --
466 *
467 * This procedure is invoked to process the Tcl command
468 * that corresponds to a widget managed by this module.
469 * See the user documentation for details on what it does.
470 *
471 * Results:
472 * A standard Tcl result.
473 *
474 * Side effects:
475 * See the user documentation.
476 *
477 *--------------------------------------------------------------
478 */
479
480 static int
481 MenuWidgetCmd (
482 ClientData clientData, /* Information about menu widget. */
483 Tcl_Interp *interp, /* Current interpreter. */
484 int argc, /* Number of arguments. */
485 char **argv /* Argument strings. */
486 )
487 {
488 register Menu *menuPtr = (Menu *) clientData;
489 register MenuEntry *mePtr;
490 int result = TCL_OK;
491 int length, type;
492 char c;
493
494 if (argc < 2) {
495 Tcl_AppendResult(interp, "wrong # args: should be \"",
496 argv[0], " option ?arg arg ...?\"", (char *) NULL);
497 return TCL_ERROR;
498 }
499 Tk_Preserve((ClientData) menuPtr);
500 c = argv[1][0];
501 length = strlen(argv[1]);
502 if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)
503 && (length >= 2)) {
504 int index;
505
506 if (argc != 3) {
507 Tcl_AppendResult(interp, "wrong # args: should be \"",
508 argv[0], " activate index\"", (char *) NULL);
509 goto error;
510 }
511 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
512 goto error;
513 }
514 if (menuPtr->active == index) {
515 goto done;
516 }
517 if (index >= 0) {
518 if ((menuPtr->entries[index]->type == SEPARATOR_ENTRY)
519 || (menuPtr->entries[index]->state == tkDisabledUid)) {
520 index = -1;
521 }
522 }
523 result = ActivateMenuEntry(menuPtr, index);
524 } else if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)
525 && (length >= 2)) {
526 MenuEntry **newEntries;
527
528 if (argc < 3) {
529 Tcl_AppendResult(interp, "wrong # args: should be \"",
530 argv[0], " add type ?options?\"", (char *) NULL);
531 goto error;
532 }
533
534 /*
535 * Figure out the type of the new entry.
536 */
537
538 c = argv[2][0];
539 length = strlen(argv[2]);
540 if ((c == 'c') && (strncmp(argv[2], "cascade", length) == 0)
541 && (length >= 2)) {
542 type = CASCADE_ENTRY;
543 } else if ((c == 'c') && (strncmp(argv[2], "checkbutton", length) == 0)
544 && (length >= 2)) {
545 type = CHECK_BUTTON_ENTRY;
546 } else if ((c == 'c') && (strncmp(argv[2], "command", length) == 0)
547 && (length >= 2)) {
548 type = COMMAND_ENTRY;
549 } else if ((c == 'r')
550 && (strncmp(argv[2], "radiobutton", length) == 0)) {
551 type = RADIO_BUTTON_ENTRY;
552 } else if ((c == 's')
553 && (strncmp(argv[2], "separator", length) == 0)) {
554 type = SEPARATOR_ENTRY;
555 } else {
556 Tcl_AppendResult(interp, "bad menu entry type \"",
557 argv[2], "\": must be cascade, checkbutton, ",
558 "command, radiobutton, or separator", (char *) NULL);
559 goto error;
560 }
561
562 /*
563 * Add a new entry to the end of the menu's array of entries,
564 * and process options for it.
565 */
566
567 mePtr = (MenuEntry *) ckalloc(sizeof(MenuEntry));
568 newEntries = (MenuEntry **) ckalloc((unsigned)
569 ((menuPtr->numEntries+1)*sizeof(MenuEntry *)));
570 if (menuPtr->numEntries != 0) {
571 memcpy((VOID *) newEntries, (VOID *) menuPtr->entries,
572 menuPtr->numEntries*sizeof(MenuEntry *));
573 ckfree((char *) menuPtr->entries);
574 }
575 menuPtr->entries = newEntries;
576 menuPtr->entries[menuPtr->numEntries] = mePtr;
577 menuPtr->numEntries++;
578 mePtr->type = type;
579 mePtr->menuPtr = menuPtr;
580 mePtr->label = NULL;
581 mePtr->underline = -1;
582 mePtr->bitmap = None;
583 mePtr->accel = NULL;
584 mePtr->state = tkNormalUid;
585 mePtr->border = NULL;
586 mePtr->activeBorder = NULL;
587 mePtr->fontPtr = NULL;
588 mePtr->textGC = None;
589 mePtr->activeGC = None;
590 mePtr->disabledGC = None;
591 mePtr->command = NULL;
592 mePtr->name = NULL;
593 mePtr->onValue = NULL;
594 mePtr->offValue = NULL;
595 mePtr->flags = 0;
596 if (ConfigureMenuEntry(interp, menuPtr, mePtr, menuPtr->numEntries-1,
597 argc-3, argv+3, 0) != TCL_OK) {
598 DestroyMenuEntry((ClientData) mePtr);
599 menuPtr->numEntries--;
600 goto error;
601 }
602 } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
603 if (argc == 2) {
604 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
605 (char *) menuPtr, (char *) NULL, 0);
606 } else if (argc == 3) {
607 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
608 (char *) menuPtr, argv[2], 0);
609 } else {
610 result = ConfigureMenu(interp, menuPtr, argc-2, argv+2,
611 TK_CONFIG_ARGV_ONLY);
612 }
613 } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
614 && (length >= 2)) {
615 int index, i;
616
617 if (argc != 3) {
618 Tcl_AppendResult(interp, "wrong # args: should be \"",
619 argv[0], " delete index\"", (char *) NULL);
620 goto error;
621 }
622 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
623 goto error;
624 }
625 if (index < 0) {
626 goto done;
627 }
628 Tk_EventuallyFree((ClientData) menuPtr->entries[index],
629 DestroyMenuEntry);
630 for (i = index; i < menuPtr->numEntries-1; i++) {
631 menuPtr->entries[i] = menuPtr->entries[i+1];
632 }
633 menuPtr->numEntries -= 1;
634 if (menuPtr->active == index) {
635 menuPtr->active = -1;
636 } else if (menuPtr->active > index) {
637 menuPtr->active -= 1;
638 }
639 if (!(menuPtr->flags & RESIZE_PENDING)) {
640 menuPtr->flags |= RESIZE_PENDING;
641 Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
642 }
643 } else if ((c == 'd') && (strncmp(argv[1], "disable", length) == 0)
644 && (length >= 2)) {
645 int index;
646
647 if (argc != 3) {
648 Tcl_AppendResult(interp, "wrong # args: should be \"",
649 argv[0], " disable index\"", (char *) NULL);
650 goto error;
651 }
652 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
653 goto error;
654 }
655 if (index < 0) {
656 goto done;
657 }
658 menuPtr->entries[index]->state = tkDisabledUid;
659 if (menuPtr->active == index) {
660 menuPtr->active = -1;
661 }
662 EventuallyRedrawMenu(menuPtr, index);
663 } else if ((c == 'e') && (length >= 3)
664 && (strncmp(argv[1], "enable", length) == 0)) {
665 int index;
666
667 if (argc != 3) {
668 Tcl_AppendResult(interp, "wrong # args: should be \"",
669 argv[0], " enable index\"", (char *) NULL);
670 goto error;
671 }
672 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
673 goto error;
674 }
675 if (index < 0) {
676 goto done;
677 }
678 menuPtr->entries[index]->state = tkNormalUid;
679 EventuallyRedrawMenu(menuPtr, index);
680 } else if ((c == 'e') && (length >= 3)
681 && (strncmp(argv[1], "entryconfigure", length) == 0)) {
682 int index;
683
684 if (argc < 3) {
685 Tcl_AppendResult(interp, "wrong # args: should be \"",
686 argv[0], " entryconfigure index ?option value ...?\"",
687 (char *) NULL);
688 goto error;
689 }
690 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
691 goto error;
692 }
693 if (index < 0) {
694 goto done;
695 }
696 mePtr = menuPtr->entries[index];
697 Tk_Preserve((ClientData) mePtr);
698 if (argc == 3) {
699 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
700 (char *) mePtr, (char *) NULL,
701 COMMAND_MASK << mePtr->type);
702 } else if (argc == 4) {
703 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
704 (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
705 } else {
706 result = ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc-3,
707 argv+3, TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
708 }
709 Tk_Release((ClientData) mePtr);
710 } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
711 && (length >= 3)) {
712 int index;
713
714 if (argc != 3) {
715 Tcl_AppendResult(interp, "wrong # args: should be \"",
716 argv[0], " index string\"", (char *) NULL);
717 goto error;
718 }
719 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
720 goto error;
721 }
722 if (index < 0) {
723 interp->result = "none";
724 } else {
725 sprintf(interp->result, "%d", index);
726 }
727 } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0)
728 && (length >= 3)) {
729 int index;
730
731 if (argc != 3) {
732 Tcl_AppendResult(interp, "wrong # args: should be \"",
733 argv[0], " invoke index\"", (char *) NULL);
734 goto error;
735 }
736 if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
737 goto error;
738 }
739 if (index < 0) {
740 goto done;
741 }
742 mePtr = menuPtr->entries[index];
743 if (mePtr->state == tkDisabledUid) {
744 goto done;
745 }
746 Tk_Preserve((ClientData) mePtr);
747 if (mePtr->type == CHECK_BUTTON_ENTRY) {
748 if (mePtr->flags & ENTRY_SELECTED) {
749 Tcl_SetVar(interp, mePtr->name, mePtr->offValue,
750 TCL_GLOBAL_ONLY);
751 } else {
752 Tcl_SetVar(interp, mePtr->name, mePtr->onValue,
753 TCL_GLOBAL_ONLY);
754 }
755 } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
756 Tcl_SetVar(interp, mePtr->name, mePtr->onValue, TCL_GLOBAL_ONLY);
757 }
758 if (mePtr->command != NULL) {
759 result = Tcl_GlobalEval(interp, mePtr->command);
760 }
761 Tk_Release((ClientData) mePtr);
762 } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0)) {
763 int x, y, tmp;
764 Tk_Uid group;
765
766 if ((argc != 4) && (argc != 5)) {
767 Tcl_AppendResult(interp, "wrong # args: should be \"",
768 argv[0], " post x y ?group?\"", (char *) NULL);
769 goto error;
770 }
771 if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK)
772 || (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) {
773 goto error;
774 }
775 if (argc == 5) {
776 group = Tk_GetUid(argv[4]);
777 } else {
778 group = Tk_GetUid("default");
779 }
780
781 /*
782 * Adjust the position of the menu if necessary to keep it
783 * on-screen.
784 */
785
786 tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin))
787 - Tk_Width(menuPtr->tkwin);
788 if (x > tmp) {
789 x = tmp;
790 }
791 if (x < 0) {
792 x = 0;
793 }
794 tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin))
795 - Tk_Height(menuPtr->tkwin);
796 if (y > tmp) {
797 y = tmp;
798 }
799 if (y < 0) {
800 y = 0;
801 }
802 if ((x != Tk_X(menuPtr->tkwin)) || (y != Tk_Y(menuPtr->tkwin))) {
803 Tk_MoveWindow(menuPtr->tkwin, x, y);
804 }
805 if (Tk_IsMapped(menuPtr->tkwin)) {
806 if (group != menuPtr->group) {
807 Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
808 Tk_ShareEvents(menuPtr->tkwin, group);
809 }
810 } else {
811 Tk_ShareEvents(menuPtr->tkwin, group);
812 Tk_MapWindow(menuPtr->tkwin);
813 result = ActivateMenuEntry(menuPtr, -1);
814 }
815 XRaiseWindow(Tk_Display(menuPtr->tkwin), Tk_WindowId(menuPtr->tkwin));
816 menuPtr->group = group;
817 } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) {
818 if (argc != 2) {
819 Tcl_AppendResult(interp, "wrong # args: should be \"",
820 argv[0], " unpost\"", (char *) NULL);
821 goto error;
822 }
823 Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
824 Tk_UnmapWindow(menuPtr->tkwin);
825 result = ActivateMenuEntry(menuPtr, -1);
826 if (result == TCL_OK) {
827 result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
828 }
829 } else {
830 Tcl_AppendResult(interp, "bad option \"", argv[1],
831 "\": must be activate, add, configure, delete, disable, ",
832 "enable, entryconfigure, index, invoke, post, ",
833 "or unpost", (char *) NULL);
834 goto error;
835 }
836 done:
837 Tk_Release((ClientData) menuPtr);
838 return result;
839
840 error:
841 Tk_Release((ClientData) menuPtr);
842 return TCL_ERROR;
843 }
844 \f
845 /*
846 *----------------------------------------------------------------------
847 *
848 * DestroyMenu --
849 *
850 * This procedure is invoked by Tk_EventuallyFree or Tk_Release
851 * to clean up the internal structure of a menu at a safe time
852 * (when no-one is using it anymore).
853 *
854 * Results:
855 * None.
856 *
857 * Side effects:
858 * Everything associated with the menu is freed up.
859 *
860 *----------------------------------------------------------------------
861 */
862
863 static void
864 DestroyMenu (
865 ClientData clientData /* Info about menu widget. */
866 )
867 {
868 register Menu *menuPtr = (Menu *) clientData;
869 int i;
870
871 for (i = 0; i < menuPtr->numEntries; i++) {
872 DestroyMenuEntry((ClientData) menuPtr->entries[i]);
873 }
874 if (menuPtr->entries != NULL) {
875 ckfree((char *) menuPtr->entries);
876 }
877 if (menuPtr->border != NULL) {
878 Tk_Free3DBorder(menuPtr->border);
879 }
880 if (menuPtr->activeBorder != NULL) {
881 Tk_Free3DBorder(menuPtr->activeBorder);
882 }
883 if (menuPtr->fontPtr != NULL) {
884 Tk_FreeFontStruct(menuPtr->fontPtr);
885 }
886 if (menuPtr->fg != NULL) {
887 Tk_FreeColor(menuPtr->fg);
888 }
889 if (menuPtr->textGC != None) {
890 Tk_FreeGC(menuPtr->textGC);
891 }
892 if (menuPtr->disabledFg != NULL) {
893 Tk_FreeColor(menuPtr->disabledFg);
894 }
895 if (menuPtr->gray != None) {
896 Tk_FreeBitmap(menuPtr->gray);
897 }
898 if (menuPtr->disabledGC != None) {
899 Tk_FreeGC(menuPtr->disabledGC);
900 }
901 if (menuPtr->activeFg != NULL) {
902 Tk_FreeColor(menuPtr->activeFg);
903 }
904 if (menuPtr->activeGC != None) {
905 Tk_FreeGC(menuPtr->activeGC);
906 }
907 if (menuPtr->selectorFg != NULL) {
908 Tk_FreeColor(menuPtr->selectorFg);
909 }
910 if (menuPtr->selectorGC != None) {
911 Tk_FreeGC(menuPtr->selectorGC);
912 }
913 if (menuPtr->cursor != None) {
914 Tk_FreeCursor(menuPtr->cursor);
915 }
916 ckfree((char *) menuPtr);
917 }
918 \f
919 /*
920 *----------------------------------------------------------------------
921 *
922 * DestroyMenuEntry --
923 *
924 * This procedure is invoked by Tk_EventuallyFree or Tk_Release
925 * to clean up the internal structure of a menu entry at a safe time
926 * (when no-one is using it anymore).
927 *
928 * Results:
929 * None.
930 *
931 * Side effects:
932 * Everything associated with the menu entry is freed up.
933 *
934 *----------------------------------------------------------------------
935 */
936
937 static void
938 DestroyMenuEntry (
939 ClientData clientData /* Pointer to entry to be freed. */
940 )
941 {
942 register MenuEntry *mePtr = (MenuEntry *) clientData;
943 Menu *menuPtr = mePtr->menuPtr;
944
945 if (mePtr->name != NULL) {
946 Tcl_UntraceVar(menuPtr->interp, mePtr->name,
947 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
948 MenuVarProc, (ClientData) mePtr);
949 }
950 if (menuPtr->postedCascade == mePtr) {
951 if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL)
952 != TCL_OK) {
953 TkBindError(menuPtr->interp);
954 }
955 }
956 if (mePtr->label != NULL) {
957 ckfree(mePtr->label);
958 }
959 if (mePtr->bitmap != None) {
960 #if defined(USE_XPM3)
961 Tk_FreePixmap(mePtr->bitmap);
962 #else
963 Tk_FreeBitmap(mePtr->bitmap);
964 #endif
965 }
966 if (mePtr->accel != NULL) {
967 ckfree(mePtr->accel);
968 }
969 if (mePtr->border != NULL) {
970 Tk_Free3DBorder(mePtr->border);
971 }
972 if (mePtr->activeBorder != NULL) {
973 Tk_Free3DBorder(mePtr->activeBorder);
974 }
975 if (mePtr->fontPtr != NULL) {
976 Tk_FreeFontStruct(mePtr->fontPtr);
977 }
978 if (mePtr->textGC != NULL) {
979 Tk_FreeGC(mePtr->textGC);
980 }
981 if (mePtr->activeGC != NULL) {
982 Tk_FreeGC(mePtr->activeGC);
983 }
984 if (mePtr->disabledGC != NULL) {
985 Tk_FreeGC(mePtr->disabledGC);
986 }
987 if (mePtr->command != NULL) {
988 ckfree(mePtr->command);
989 }
990 if (mePtr->name != NULL) {
991 ckfree(mePtr->name);
992 }
993 if (mePtr->onValue != NULL) {
994 ckfree(mePtr->onValue);
995 }
996 if (mePtr->offValue != NULL) {
997 ckfree(mePtr->offValue);
998 }
999 ckfree((char *) mePtr);
1000 }
1001 \f
1002 /*
1003 *----------------------------------------------------------------------
1004 *
1005 * ConfigureMenu --
1006 *
1007 * This procedure is called to process an argv/argc list, plus
1008 * the Tk option database, in order to configure (or
1009 * reconfigure) a menu widget.
1010 *
1011 * Results:
1012 * The return value is a standard Tcl result. If TCL_ERROR is
1013 * returned, then interp->result contains an error message.
1014 *
1015 * Side effects:
1016 * Configuration information, such as colors, font, etc. get set
1017 * for menuPtr; old resources get freed, if there were any.
1018 *
1019 *----------------------------------------------------------------------
1020 */
1021
1022 static int
1023 ConfigureMenu (
1024 Tcl_Interp *interp, /* Used for error reporting. */
1025 register Menu *menuPtr, /* Information about widget; may or may
1026 * not already have values for some fields. */
1027 int argc, /* Number of valid entries in argv. */
1028 char **argv, /* Arguments. */
1029 int flags /* Flags to pass to Tk_ConfigureWidget. */
1030 )
1031 {
1032 XGCValues gcValues;
1033 GC newGC;
1034 unsigned long mask;
1035 int i;
1036
1037 if (Tk_ConfigureWidget(interp, menuPtr->tkwin, configSpecs,
1038 argc, argv, (char *) menuPtr, flags) != TCL_OK) {
1039 return TCL_ERROR;
1040 }
1041
1042 /*
1043 * A few options need special processing, such as setting the
1044 * background from a 3-D border, or filling in complicated
1045 * defaults that couldn't be specified to Tk_ConfigureWidget.
1046 */
1047
1048 Tk_SetBackgroundFromBorder(menuPtr->tkwin, menuPtr->border);
1049
1050 gcValues.font = menuPtr->fontPtr->fid;
1051 gcValues.foreground = menuPtr->fg->pixel;
1052 gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel;
1053 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
1054 &gcValues);
1055 if (menuPtr->textGC != None) {
1056 Tk_FreeGC(menuPtr->textGC);
1057 }
1058 menuPtr->textGC = newGC;
1059
1060 if (menuPtr->disabledFg != NULL) {
1061 gcValues.foreground = menuPtr->disabledFg->pixel;
1062 mask = GCForeground|GCBackground|GCFont;
1063 } else {
1064 gcValues.foreground = gcValues.background;
1065 if (menuPtr->gray == None) {
1066 menuPtr->gray = Tk_GetBitmap(interp, menuPtr->tkwin,
1067 Tk_GetUid("gray50"));
1068 if (menuPtr->gray == None) {
1069 return TCL_ERROR;
1070 }
1071 }
1072 gcValues.fill_style = FillStippled;
1073 gcValues.stipple = menuPtr->gray;
1074 mask = GCForeground|GCFillStyle|GCStipple;
1075 }
1076 newGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
1077 if (menuPtr->disabledGC != None) {
1078 Tk_FreeGC(menuPtr->disabledGC);
1079 }
1080 menuPtr->disabledGC = newGC;
1081
1082 gcValues.font = menuPtr->fontPtr->fid;
1083 gcValues.foreground = menuPtr->activeFg->pixel;
1084 gcValues.background = Tk_3DBorderColor(menuPtr->activeBorder)->pixel;
1085 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
1086 &gcValues);
1087 if (menuPtr->activeGC != None) {
1088 Tk_FreeGC(menuPtr->activeGC);
1089 }
1090 menuPtr->activeGC = newGC;
1091
1092 gcValues.foreground = menuPtr->selectorFg->pixel;
1093 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCFont, &gcValues);
1094 if (menuPtr->selectorGC != None) {
1095 Tk_FreeGC(menuPtr->selectorGC);
1096 }
1097 menuPtr->selectorGC = newGC;
1098
1099 /*
1100 * After reconfiguring a menu, we need to reconfigure all of the
1101 * entries in the menu, since some of the things in the children
1102 * (such as graphics contexts) may have to change to reflect changes
1103 * in the parent.
1104 */
1105
1106 for (i = 0; i < menuPtr->numEntries; i++) {
1107 MenuEntry *mePtr;
1108
1109 mePtr = menuPtr->entries[i];
1110 ConfigureMenuEntry(interp, menuPtr, mePtr, i, 0, (char **) NULL,
1111 TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
1112 }
1113
1114 if (!(menuPtr->flags & RESIZE_PENDING)) {
1115 menuPtr->flags |= RESIZE_PENDING;
1116 Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
1117 }
1118
1119 return TCL_OK;
1120 }
1121 \f
1122 /*
1123 *----------------------------------------------------------------------
1124 *
1125 * ConfigureMenuEntry --
1126 *
1127 * This procedure is called to process an argv/argc list, plus
1128 * the Tk option database, in order to configure (or
1129 * reconfigure) one entry in a menu.
1130 *
1131 * Results:
1132 * The return value is a standard Tcl result. If TCL_ERROR is
1133 * returned, then interp->result contains an error message.
1134 *
1135 * Side effects:
1136 * Configuration information such as label and accelerator get
1137 * set for mePtr; old resources get freed, if there were any.
1138 *
1139 *----------------------------------------------------------------------
1140 */
1141
1142 static int
1143 ConfigureMenuEntry (
1144 Tcl_Interp *interp, /* Used for error reporting. */
1145 Menu *menuPtr, /* Information about whole menu. */
1146 register MenuEntry *mePtr, /* Information about menu entry; may
1147 * or may not already have values for
1148 * some fields. */
1149 int index, /* Index of mePtr within menuPtr's
1150 * entries. */
1151 int argc, /* Number of valid entries in argv. */
1152 char **argv, /* Arguments. */
1153 int flags /* Additional flags to pass to
1154 * Tk_ConfigureWidget. */
1155 )
1156 {
1157 XGCValues gcValues;
1158 GC newGC, newActiveGC, newDisabledGC;
1159 unsigned long mask;
1160
1161 /*
1162 * If this entry is a cascade and the cascade is posted, then unpost
1163 * it before reconfiguring the entry (otherwise the reconfigure might
1164 * change the name of the cascaded entry, leaving a posted menu
1165 * high and dry).
1166 */
1167
1168 if (menuPtr->postedCascade == mePtr) {
1169 if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL)
1170 != TCL_OK) {
1171 TkBindError(menuPtr->interp);
1172 }
1173 }
1174
1175 /*
1176 * If this entry is a check button or radio button, then remove
1177 * its old trace procedure.
1178 */
1179
1180 if ((mePtr->name != NULL) &&
1181 ((mePtr->type == CHECK_BUTTON_ENTRY)
1182 || (mePtr->type == RADIO_BUTTON_ENTRY))) {
1183 Tcl_UntraceVar(menuPtr->interp, mePtr->name,
1184 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1185 MenuVarProc, (ClientData) mePtr);
1186 }
1187
1188 if (Tk_ConfigureWidget(interp, menuPtr->tkwin, entryConfigSpecs,
1189 argc, argv, (char *) mePtr,
1190 flags | (COMMAND_MASK << mePtr->type)) != TCL_OK) {
1191 return TCL_ERROR;
1192 }
1193
1194 /*
1195 * The code below handles special configuration stuff not taken
1196 * care of by Tk_ConfigureWidget, such as special processing for
1197 * defaults, sizing strings, graphics contexts, etc.
1198 */
1199
1200 if (mePtr->label == NULL) {
1201 mePtr->labelLength = 0;
1202 } else {
1203 mePtr->labelLength = strlen(mePtr->label);
1204 }
1205 if (mePtr->accel == NULL) {
1206 mePtr->accelLength = 0;
1207 } else {
1208 mePtr->accelLength = strlen(mePtr->accel);
1209 }
1210
1211 if (mePtr->state == tkActiveUid) {
1212 if (index != menuPtr->active) {
1213 ActivateMenuEntry(menuPtr, index);
1214 }
1215 } else {
1216 if (index == menuPtr->active) {
1217 ActivateMenuEntry(menuPtr, -1);
1218 }
1219 if ((mePtr->state != tkNormalUid) && (mePtr->state != tkDisabledUid)) {
1220 Tcl_AppendResult(interp, "bad state value \"", mePtr->state,
1221 "\": must be normal, active, or disabled", (char *) NULL);
1222 mePtr->state = tkNormalUid;
1223 return TCL_ERROR;
1224 }
1225 }
1226
1227 if (mePtr->fontPtr != NULL) {
1228 gcValues.foreground = menuPtr->fg->pixel;
1229 gcValues.background = Tk_3DBorderColor(
1230 (mePtr->border != NULL) ? mePtr->border : menuPtr->border)
1231 ->pixel;
1232 gcValues.font = mePtr->fontPtr->fid;
1233
1234 /*
1235 * Note: disable GraphicsExpose events; we know there won't be
1236 * obscured areas when copying from an off-screen pixmap to the
1237 * screen and this gets rid of unnecessary events.
1238 */
1239
1240 gcValues.graphics_exposures = False;
1241 newGC = Tk_GetGC(menuPtr->tkwin,
1242 GCForeground|GCBackground|GCFont|GCGraphicsExposures,
1243 &gcValues);
1244
1245 if (menuPtr->disabledFg != NULL) {
1246 gcValues.foreground = menuPtr->disabledFg->pixel;
1247 mask = GCForeground|GCBackground|GCFont|GCGraphicsExposures;
1248 } else {
1249 gcValues.foreground = gcValues.background;
1250 gcValues.fill_style = FillStippled;
1251 gcValues.stipple = menuPtr->gray;
1252 mask = GCForeground|GCFillStyle|GCStipple;
1253 }
1254 newDisabledGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
1255
1256 gcValues.foreground = menuPtr->activeFg->pixel;
1257 gcValues.background = Tk_3DBorderColor(
1258 (mePtr->activeBorder != NULL) ? mePtr->activeBorder
1259 : menuPtr->activeBorder)->pixel;
1260 newActiveGC = Tk_GetGC(menuPtr->tkwin,
1261 GCForeground|GCBackground|GCFont|GCGraphicsExposures,
1262 &gcValues);
1263 } else {
1264 newGC = NULL;
1265 newActiveGC = NULL;
1266 newDisabledGC = NULL;
1267 }
1268 if (mePtr->textGC != NULL) {
1269 Tk_FreeGC(mePtr->textGC);
1270 }
1271 mePtr->textGC = newGC;
1272 if (mePtr->activeGC != NULL) {
1273 Tk_FreeGC(mePtr->activeGC);
1274 }
1275 mePtr->activeGC = newActiveGC;
1276 if (mePtr->disabledGC != NULL) {
1277 Tk_FreeGC(mePtr->disabledGC);
1278 }
1279 mePtr->disabledGC = newDisabledGC;
1280
1281 if ((mePtr->type == CHECK_BUTTON_ENTRY)
1282 || (mePtr->type == RADIO_BUTTON_ENTRY)) {
1283 char *value;
1284
1285 if (mePtr->name == NULL) {
1286 mePtr->name = ckalloc((unsigned) (strlen(mePtr->label) + 1));
1287 strcpy(mePtr->name, mePtr->label);
1288 }
1289 if (mePtr->onValue == NULL) {
1290 mePtr->onValue = ckalloc((unsigned) (strlen(mePtr->label) + 1));
1291 strcpy(mePtr->onValue, mePtr->label);
1292 }
1293
1294 /*
1295 * Select the entry if the associated variable has the
1296 * appropriate value, initialize the variable if it doesn't
1297 * exist, then set a trace on the variable to monitor future
1298 * changes to its value.
1299 */
1300
1301 value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY);
1302 mePtr->flags &= ENTRY_SELECTED;
1303 if (value != NULL) {
1304 if (strcmp(value, mePtr->onValue) == 0) {
1305 mePtr->flags |= ENTRY_SELECTED;
1306 }
1307 } else {
1308 Tcl_SetVar(interp, mePtr->name,
1309 (mePtr->type == CHECK_BUTTON_ENTRY) ? mePtr->offValue : "",
1310 TCL_GLOBAL_ONLY);
1311 }
1312 Tcl_TraceVar(interp, mePtr->name,
1313 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1314 MenuVarProc, (ClientData) mePtr);
1315 }
1316
1317 if (!(menuPtr->flags & RESIZE_PENDING)) {
1318 menuPtr->flags |= RESIZE_PENDING;
1319 Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
1320 }
1321 return TCL_OK;
1322 }
1323 \f
1324 /*
1325 *--------------------------------------------------------------
1326 *
1327 * ComputeMenuGeometry --
1328 *
1329 * This procedure is invoked to recompute the size and
1330 * layout of a menu. It is called as a when-idle handler so
1331 * that it only gets done once, even if a group of changes is
1332 * made to the menu.
1333 *
1334 * Results:
1335 * None.
1336 *
1337 * Side effects:
1338 * Fields of menu entries are changed to reflect their
1339 * current positions, and the size of the menu window
1340 * itself may be changed.
1341 *
1342 *--------------------------------------------------------------
1343 */
1344
1345 static void
1346 ComputeMenuGeometry (
1347 ClientData clientData /* Structure describing menu. */
1348 )
1349 {
1350 Menu *menuPtr = (Menu *) clientData;
1351 register MenuEntry *mePtr;
1352 XFontStruct *fontPtr;
1353 int maxLabelWidth, maxSelectorWidth, maxAccelWidth;
1354 int width, height, selectorSpace, horizMargin;
1355 int i, y;
1356
1357 if (menuPtr->tkwin == NULL) {
1358 return;
1359 }
1360
1361 maxLabelWidth = maxSelectorWidth = maxAccelWidth = 0;
1362 y = menuPtr->borderWidth;
1363
1364 for (i = 0; i < menuPtr->numEntries; i++) {
1365 mePtr = menuPtr->entries[i];
1366 selectorSpace = 0;
1367 fontPtr = mePtr->fontPtr;
1368 if (fontPtr == NULL) {
1369 fontPtr = menuPtr->fontPtr;
1370 }
1371
1372 /*
1373 * For each entry, compute the height required by that
1374 * particular entry, plus three widths: the width of the
1375 * label, the width to allow for a selector to be displayed
1376 * to the left of the label (if any), and the width of the
1377 * accelerator to be displayed to the right of the label
1378 * (if any). These sizes depend, of course, on the type
1379 * of the entry.
1380 */
1381
1382 if (mePtr->bitmap != None) {
1383 unsigned int bitmapWidth, bitmapHeight;
1384
1385 #if defined(USE_XPM3)
1386 Tk_SizeOfPixmap(mePtr->bitmap, &bitmapWidth, &bitmapHeight);
1387 #else
1388 Tk_SizeOfBitmap(mePtr->bitmap, &bitmapWidth, &bitmapHeight);
1389 #endif
1390 mePtr->height = bitmapHeight;
1391 width = bitmapWidth;
1392 if (mePtr->type == CHECK_BUTTON_ENTRY) {
1393 selectorSpace = (14*mePtr->height)/10;
1394 mePtr->selectorDiameter = (65*mePtr->height)/100;
1395 } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
1396 selectorSpace = (14*mePtr->height)/10;
1397 mePtr->selectorDiameter = (75*mePtr->height)/100;
1398 }
1399 } else {
1400 mePtr->height = fontPtr->ascent + fontPtr->descent;
1401 if (mePtr->label != NULL) {
1402 (void) TkMeasureChars(fontPtr, mePtr->label,
1403 mePtr->labelLength, 0, (int) 100000,
1404 TK_NEWLINES_NOT_SPECIAL, &width);
1405 } else {
1406 width = 0;
1407 }
1408 if (mePtr->type == CHECK_BUTTON_ENTRY) {
1409 selectorSpace = mePtr->height;
1410 mePtr->selectorDiameter = (80*mePtr->height)/100;
1411 } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
1412 selectorSpace = mePtr->height;
1413 mePtr->selectorDiameter = mePtr->height;
1414 }
1415 }
1416 mePtr->height += 2*menuPtr->activeBorderWidth + 2;
1417 if (width > maxLabelWidth) {
1418 maxLabelWidth = width;
1419 }
1420 if (mePtr->accel != NULL) {
1421 (void) TkMeasureChars(fontPtr, mePtr->accel, mePtr->accelLength,
1422 0, (int) 100000, TK_NEWLINES_NOT_SPECIAL, &width);
1423 if (width > maxAccelWidth) {
1424 maxAccelWidth = width;
1425 }
1426 }
1427 if (mePtr->type == SEPARATOR_ENTRY) {
1428 mePtr->height = 4*menuPtr->borderWidth;
1429 }
1430 if (selectorSpace > maxSelectorWidth) {
1431 maxSelectorWidth = selectorSpace;
1432 }
1433 mePtr->y = y;
1434 y += mePtr->height;
1435 }
1436
1437 /*
1438 * Got all the sizes. Update fields in the menu structure, then
1439 * resize the window if necessary. Leave margins on either side
1440 * of the selector (or just one margin if there is no selector).
1441 * Leave another margin on the right side of the label, plus yet
1442 * another margin to the right of the accelerator (if there is one).
1443 */
1444
1445 horizMargin = 2;
1446 menuPtr->selectorSpace = maxSelectorWidth + horizMargin;
1447 if (maxSelectorWidth != 0) {
1448 menuPtr->selectorSpace += horizMargin;
1449 }
1450 menuPtr->labelWidth = maxLabelWidth + horizMargin;
1451 width = menuPtr->selectorSpace + menuPtr->labelWidth + maxAccelWidth
1452 + 2*menuPtr->borderWidth + 2*menuPtr->activeBorderWidth + 2;
1453 if (maxAccelWidth != 0) {
1454 width += horizMargin;
1455 }
1456 height = y + menuPtr->borderWidth;
1457
1458 /*
1459 * The X server doesn't like zero dimensions, so round up to at least
1460 * 1 (a zero-sized menu should never really occur, anyway).
1461 */
1462
1463 if (width <= 0) {
1464 width = 1;
1465 }
1466 if (height <= 0) {
1467 height = 1;
1468 }
1469 if ((width != Tk_ReqWidth(menuPtr->tkwin)) ||
1470 (height != Tk_ReqHeight(menuPtr->tkwin))) {
1471 Tk_GeometryRequest(menuPtr->tkwin, width, height);
1472 } else {
1473 /*
1474 * Must always force a redisplay here if the window is mapped
1475 * (even if the size didn't change, something else might have
1476 * changed in the menu, such as a label or accelerator). The
1477 * resize will force a redisplay above.
1478 */
1479
1480 EventuallyRedrawMenu(menuPtr, -1);
1481 }
1482
1483 menuPtr->flags &= ~RESIZE_PENDING;
1484 }
1485 \f
1486 /*
1487 *----------------------------------------------------------------------
1488 *
1489 * DisplayMenu --
1490 *
1491 * This procedure is invoked to display a menu widget.
1492 *
1493 * Results:
1494 * None.
1495 *
1496 * Side effects:
1497 * Commands are output to X to display the menu in its
1498 * current mode.
1499 *
1500 *----------------------------------------------------------------------
1501 */
1502
1503 static void
1504 DisplayMenu (
1505 ClientData clientData /* Information about widget. */
1506 )
1507 {
1508 register Menu *menuPtr = (Menu *) clientData;
1509 register MenuEntry *mePtr;
1510 register Tk_Window tkwin = menuPtr->tkwin;
1511 XFontStruct *fontPtr;
1512 int index, baseline;
1513 GC gc;
1514
1515 menuPtr->flags &= ~REDRAW_PENDING;
1516 if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
1517 return;
1518 }
1519
1520 /*
1521 * Loop through all of the entries, drawing them one at a time.
1522 */
1523
1524 for (index = 0; index < menuPtr->numEntries; index++) {
1525 mePtr = menuPtr->entries[index];
1526 if (!(mePtr->flags & ENTRY_NEEDS_REDISPLAY)) {
1527 continue;
1528 }
1529 mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY;
1530
1531 /*
1532 * Background.
1533 */
1534
1535 if (mePtr->state == tkActiveUid) {
1536 Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1537 (mePtr->activeBorder != NULL) ? mePtr->activeBorder
1538 : menuPtr->activeBorder, menuPtr->borderWidth, mePtr->y,
1539 Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
1540 menuPtr->activeBorderWidth, TK_RELIEF_RAISED);
1541 gc = mePtr->activeGC;
1542 if (gc == NULL) {
1543 gc = menuPtr->activeGC;
1544 }
1545 } else {
1546 Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1547 (mePtr->border != NULL) ? mePtr->border
1548 : menuPtr->border, menuPtr->borderWidth, mePtr->y,
1549 Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
1550 0, TK_RELIEF_FLAT);
1551 if ((mePtr->state == tkDisabledUid)
1552 && (menuPtr->disabledFg != NULL)) {
1553 gc = mePtr->disabledGC;
1554 if (gc == NULL) {
1555 gc = menuPtr->disabledGC;
1556 }
1557 } else {
1558 gc = mePtr->textGC;
1559 if (gc == NULL) {
1560 gc = menuPtr->textGC;
1561 }
1562 }
1563 }
1564
1565 /*
1566 * Draw label or bitmap for entry.
1567 */
1568
1569 fontPtr = mePtr->fontPtr;
1570 if (fontPtr == NULL) {
1571 fontPtr = menuPtr->fontPtr;
1572 }
1573 baseline = mePtr->y + (mePtr->height + fontPtr->ascent
1574 - fontPtr->descent)/2;
1575 if (mePtr->bitmap != None) {
1576 unsigned int width, height;
1577
1578 #if defined(USE_XPM3)
1579 Tk_SizeOfPixmap(mePtr->bitmap, &width, &height);
1580 XCopyArea(Tk_Display(tkwin), mePtr->bitmap, Tk_WindowId(tkwin),
1581 gc, 0, 0, width, height,
1582 menuPtr->borderWidth + menuPtr->selectorSpace,
1583 (int) (mePtr->y + (mePtr->height - height)/2));
1584 #else
1585 Tk_SizeOfBitmap(mePtr->bitmap, &width, &height);
1586 XCopyPlane(Tk_Display(tkwin), mePtr->bitmap, Tk_WindowId(tkwin),
1587 gc, 0, 0, width, height,
1588 menuPtr->borderWidth + menuPtr->selectorSpace,
1589 (int) (mePtr->y + (mePtr->height - height)/2), 1);
1590 #endif
1591 } else {
1592 baseline = mePtr->y + (mePtr->height + fontPtr->ascent
1593 - fontPtr->descent)/2;
1594 if (mePtr->label != NULL) {
1595 TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
1596 fontPtr, mePtr->label, mePtr->labelLength,
1597 menuPtr->borderWidth + menuPtr->selectorSpace,
1598 baseline, TK_NEWLINES_NOT_SPECIAL);
1599 if (mePtr->underline >= 0) {
1600 TkUnderlineChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
1601 fontPtr, mePtr->label,
1602 menuPtr->borderWidth + menuPtr->selectorSpace,
1603 baseline, TK_NEWLINES_NOT_SPECIAL,
1604 mePtr->underline, mePtr->underline);
1605 }
1606 }
1607 }
1608
1609 /*
1610 * Draw accelerator.
1611 */
1612
1613 if (mePtr->accel != NULL) {
1614 TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
1615 fontPtr, mePtr->accel, mePtr->accelLength,
1616 menuPtr->borderWidth + menuPtr->selectorSpace
1617 + menuPtr->labelWidth, baseline, TK_NEWLINES_NOT_SPECIAL);
1618 }
1619
1620 /*
1621 * Draw check-button selector.
1622 */
1623
1624 if (mePtr->type == CHECK_BUTTON_ENTRY) {
1625 int dim, x, y;
1626
1627 dim = mePtr->selectorDiameter;
1628 x = menuPtr->borderWidth + (menuPtr->selectorSpace - dim)/2;
1629 y = mePtr->y + (mePtr->height - dim)/2;
1630 Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1631 menuPtr->border, x, y, dim, dim,
1632 menuPtr->activeBorderWidth, TK_RELIEF_SUNKEN);
1633 x += menuPtr->activeBorderWidth;
1634 y += menuPtr->activeBorderWidth;
1635 dim -= 2*menuPtr->activeBorderWidth;
1636 if ((dim > 0) && (mePtr->flags & ENTRY_SELECTED)) {
1637 XFillRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1638 menuPtr->selectorGC, x, y, (unsigned int) dim,
1639 (unsigned int) dim);
1640 }
1641 }
1642
1643 /*
1644 * Draw radio-button selector.
1645 */
1646
1647 if (mePtr->type == RADIO_BUTTON_ENTRY) {
1648 XPoint points[4];
1649 int radius;
1650
1651 radius = mePtr->selectorDiameter/2;
1652 points[0].x = menuPtr->borderWidth
1653 + (menuPtr->selectorSpace - mePtr->selectorDiameter)/2;
1654 points[0].y = mePtr->y + (mePtr->height)/2;
1655 points[1].x = points[0].x + radius;
1656 points[1].y = points[0].y + radius;
1657 points[2].x = points[1].x + radius;
1658 points[2].y = points[0].y;
1659 points[3].x = points[1].x;
1660 points[3].y = points[0].y - radius;
1661 if (mePtr->flags & ENTRY_SELECTED) {
1662 XFillPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
1663 menuPtr->selectorGC, points, 4, Convex,
1664 CoordModeOrigin);
1665 } else {
1666 Tk_Fill3DPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
1667 menuPtr->border, points, 4, menuPtr->activeBorderWidth,
1668 TK_RELIEF_FLAT);
1669 }
1670 Tk_Draw3DPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
1671 menuPtr->border, points, 4, menuPtr->activeBorderWidth,
1672 TK_RELIEF_SUNKEN);
1673 }
1674
1675 /*
1676 * Draw separator.
1677 */
1678
1679 if (mePtr->type == SEPARATOR_ENTRY) {
1680 XPoint points[2];
1681 int margin;
1682
1683 margin = (fontPtr->ascent + fontPtr->descent)/2;
1684 points[0].x = 2*menuPtr->borderWidth + margin;
1685 points[0].y = mePtr->y + mePtr->height/2;
1686 points[1].x = Tk_Width(tkwin) - 2*menuPtr->borderWidth - margin;
1687 points[1].y = points[0].y;
1688 Tk_Draw3DPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
1689 menuPtr->border, points, 2, 1, TK_RELIEF_RAISED);
1690 }
1691
1692 /*
1693 * If the entry is disabled with a stipple rather than a special
1694 * foreground color, generate the stippled effect.
1695 */
1696
1697 if ((mePtr->state == tkDisabledUid) && (menuPtr->disabledFg == NULL)) {
1698 XFillRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1699 menuPtr->disabledGC, menuPtr->borderWidth,
1700 mePtr->y,
1701 (unsigned) (Tk_Width(tkwin) - 2*menuPtr->borderWidth),
1702 (unsigned) mePtr->height);
1703 }
1704 }
1705
1706 Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1707 menuPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
1708 menuPtr->borderWidth, TK_RELIEF_RAISED);
1709 }
1710 \f
1711 /*
1712 *--------------------------------------------------------------
1713 *
1714 * GetMenuIndex --
1715 *
1716 * Parse a textual index into a menu and return the numerical
1717 * index of the indicated entry.
1718 *
1719 * Results:
1720 * A standard Tcl result. If all went well, then *indexPtr is
1721 * filled in with the entry index corresponding to string
1722 * (ranges from -1 to the number of entries in the menu minus
1723 * one). Otherwise an error message is left in interp->result.
1724 *
1725 * Side effects:
1726 * None.
1727 *
1728 *--------------------------------------------------------------
1729 */
1730
1731 static int
1732 GetMenuIndex (
1733 Tcl_Interp *interp, /* For error messages. */
1734 Menu *menuPtr, /* Menu for which the index is being
1735 * specified. */
1736 char *string, /* Specification of an entry in menu. See
1737 * manual entry for valid .*/
1738 int *indexPtr /* Where to store converted relief. */
1739 )
1740 {
1741 int i, y;
1742
1743 if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
1744 *indexPtr = menuPtr->active;
1745 return TCL_OK;
1746 }
1747
1748 if ((string[0] == 'l') && (strcmp(string, "last") == 0)) {
1749 *indexPtr = menuPtr->numEntries-1;
1750 return TCL_OK;
1751 }
1752
1753 if ((string[0] == 'n') && (strcmp(string, "none") == 0)) {
1754 *indexPtr = -1;
1755 return TCL_OK;
1756 }
1757
1758 if (string[0] == '@') {
1759 if (Tcl_GetInt(interp, string+1, &y) == TCL_OK) {
1760 if (y < 0) {
1761 *indexPtr = -1;
1762 return TCL_OK;
1763 }
1764 for (i = 0; i < menuPtr->numEntries; i++) {
1765 y -= menuPtr->entries[i]->height;
1766 if (y < 0) {
1767 break;
1768 }
1769 }
1770 if (i >= menuPtr->numEntries) {
1771 i = -1;
1772 }
1773 *indexPtr = i;
1774 return TCL_OK;
1775 } else {
1776 Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
1777 }
1778 }
1779
1780 if (isdigit(string[0])) {
1781 if (Tcl_GetInt(interp, string, &i) == TCL_OK) {
1782 if ((i < menuPtr->numEntries) && (i >= 0)) {
1783 *indexPtr = i;
1784 return TCL_OK;
1785 }
1786 } else {
1787 Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
1788 }
1789 }
1790
1791 for (i = 0; i < menuPtr->numEntries; i++) {
1792 char *label;
1793
1794 label = menuPtr->entries[i]->label;
1795 if ((label != NULL)
1796 && (Tcl_StringMatch(menuPtr->entries[i]->label, string))) {
1797 *indexPtr = i;
1798 return TCL_OK;
1799 }
1800 }
1801
1802 Tcl_AppendResult(interp, "bad menu entry index \"",
1803 string, "\"", (char *) NULL);
1804 return TCL_ERROR;
1805 }
1806 \f
1807 /*
1808 *--------------------------------------------------------------
1809 *
1810 * MenuEventProc --
1811 *
1812 * This procedure is invoked by the Tk dispatcher for various
1813 * events on menus.
1814 *
1815 * Results:
1816 * None.
1817 *
1818 * Side effects:
1819 * When the window gets deleted, internal structures get
1820 * cleaned up. When it gets exposed, it is redisplayed.
1821 *
1822 *--------------------------------------------------------------
1823 */
1824
1825 static void
1826 MenuEventProc (
1827 ClientData clientData, /* Information about window. */
1828 XEvent *eventPtr /* Information about event. */
1829 )
1830 {
1831 Menu *menuPtr = (Menu *) clientData;
1832 if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
1833 EventuallyRedrawMenu(menuPtr, -1);
1834 } else if (eventPtr->type == DestroyNotify) {
1835 Tcl_DeleteCommand(menuPtr->interp, Tk_PathName(menuPtr->tkwin));
1836
1837 /*
1838 * Careful! Must delete the event-sharing information here
1839 * rather than in DestroyMenu. By the time that procedure
1840 * is called the tkwin may have been reused, resulting in some
1841 * other window accidentally being cut off from shared events.
1842 */
1843
1844 Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
1845 menuPtr->tkwin = NULL;
1846 if (menuPtr->flags & REDRAW_PENDING) {
1847 Tk_CancelIdleCall(DisplayMenu, (ClientData) menuPtr);
1848 }
1849 if (menuPtr->flags & RESIZE_PENDING) {
1850 Tk_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
1851 }
1852 Tk_EventuallyFree((ClientData) menuPtr, DestroyMenu);
1853 }
1854 }
1855 \f
1856 /*
1857 *--------------------------------------------------------------
1858 *
1859 * MenuVarProc --
1860 *
1861 * This procedure is invoked when someone changes the
1862 * state variable associated with a radiobutton or checkbutton
1863 * menu entry. The entry's selected state is set to match
1864 * the value of the variable.
1865 *
1866 * Results:
1867 * NULL is always returned.
1868 *
1869 * Side effects:
1870 * The menu entry may become selected or deselected.
1871 *
1872 *--------------------------------------------------------------
1873 */
1874
1875 /* ARGSUSED */
1876 static char *
1877 MenuVarProc (
1878 ClientData clientData, /* Information about menu entry. */
1879 Tcl_Interp *interp, /* Interpreter containing variable. */
1880 char *name1, /* First part of variable's name. */
1881 char *name2, /* Second part of variable's name. */
1882 int flags /* Describes what just happened. */
1883 )
1884 {
1885 MenuEntry *mePtr = (MenuEntry *) clientData;
1886 Menu *menuPtr;
1887 char *value;
1888
1889 menuPtr = mePtr->menuPtr;
1890
1891 /*
1892 * If the variable is being unset, then re-establish the
1893 * trace unless the whole interpreter is going away.
1894 */
1895
1896 if (flags & TCL_TRACE_UNSETS) {
1897 mePtr->flags &= ~ENTRY_SELECTED;
1898 if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
1899 Tcl_TraceVar2(interp, name1, name2,
1900 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1901 MenuVarProc, clientData);
1902 }
1903 EventuallyRedrawMenu(menuPtr, -1);
1904 return (char *) NULL;
1905 }
1906
1907 /*
1908 * Use the value of the variable to update the selected status of
1909 * the menu entry.
1910 */
1911
1912 value = Tcl_GetVar2(interp, name1, name2, flags & TCL_GLOBAL_ONLY);
1913 if (strcmp(value, mePtr->onValue) == 0) {
1914 if (mePtr->flags & ENTRY_SELECTED) {
1915 return (char *) NULL;
1916 }
1917 mePtr->flags |= ENTRY_SELECTED;
1918 } else if (mePtr->flags & ENTRY_SELECTED) {
1919 mePtr->flags &= ~ENTRY_SELECTED;
1920 } else {
1921 return (char *) NULL;
1922 }
1923 EventuallyRedrawMenu(menuPtr, -1);
1924 return (char *) NULL;
1925 }
1926 \f
1927 /*
1928 *----------------------------------------------------------------------
1929 *
1930 * EventuallyRedrawMenu --
1931 *
1932 * Arrange for an entry of a menu, or the whole menu, to be
1933 * redisplayed at some point in the future.
1934 *
1935 * Results:
1936 * None.
1937 *
1938 * Side effects:
1939 * A when-idle hander is scheduled to do the redisplay, if there
1940 * isn't one already scheduled.
1941 *
1942 *----------------------------------------------------------------------
1943 */
1944
1945 static void
1946 EventuallyRedrawMenu (
1947 register Menu *menuPtr, /* Information about menu to redraw. */
1948 int index /* Which entry to redraw. If -1, then
1949 * all the entries in the menu are redrawn. */
1950 )
1951 {
1952 if (menuPtr->tkwin == NULL) {
1953 return;
1954 }
1955 if (index != -1) {
1956 menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
1957 } else {
1958 for (index = 0; index < menuPtr->numEntries; index++) {
1959 menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
1960 }
1961 }
1962 if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)
1963 || (menuPtr->flags & REDRAW_PENDING)) {
1964 return;
1965 }
1966 Tk_DoWhenIdle(DisplayMenu, (ClientData) menuPtr);
1967 menuPtr->flags |= REDRAW_PENDING;
1968 }
1969 \f
1970 /*
1971 *--------------------------------------------------------------
1972 *
1973 * PostSubmenu --
1974 *
1975 * This procedure arranges for a particular submenu (i.e. the
1976 * menu corresponding to a given cascade entry) to be
1977 * posted.
1978 *
1979 * Results:
1980 * A standard Tcl return result. Errors may occur in the
1981 * Tcl commands generated to post and unpost submenus.
1982 *
1983 * Side effects:
1984 * If there is already a submenu posted, it is unposted.
1985 * The new submenu is then posted.
1986 *
1987 *--------------------------------------------------------------
1988 */
1989
1990 static int
1991 PostSubmenu (
1992 Tcl_Interp *interp, /* Used for invoking sub-commands and
1993 * reporting errors. */
1994 register Menu *menuPtr, /* Information about menu as a whole. */
1995 register MenuEntry *mePtr /* Info about submenu that is to be
1996 * posted. NULL means make sure that
1997 * no submenu is posted. */
1998 )
1999 {
2000 char string[30];
2001 int result, x, y;
2002
2003 if (mePtr == menuPtr->postedCascade) {
2004 return TCL_OK;
2005 }
2006
2007 if (menuPtr->postedCascade != NULL) {
2008 result = Tcl_VarEval(interp, menuPtr->postedCascade->name,
2009 " unpost", (char *) NULL);
2010 menuPtr->postedCascade = NULL;
2011 if (result != TCL_OK) {
2012 return result;
2013 }
2014 }
2015
2016 if ((mePtr != NULL) && (mePtr->name != NULL)) {
2017 Tk_GetRootCoords(menuPtr->tkwin, &x, &y);
2018 x += Tk_Width(menuPtr->tkwin);
2019 y += mePtr->y;
2020 sprintf(string, "%d %d ", x, y);
2021 result = Tcl_VarEval(interp, mePtr->name, " post ", string,
2022 menuPtr->group, (char *) NULL);
2023 if (result != TCL_OK) {
2024 return result;
2025 }
2026 menuPtr->postedCascade = mePtr;
2027 }
2028 return TCL_OK;
2029 }
2030 \f
2031 /*
2032 *----------------------------------------------------------------------
2033 *
2034 * ActivateMenuEntry --
2035 *
2036 * This procedure is invoked to make a particular menu entry
2037 * the active one, deactivating any other entry that might
2038 * currently be active.
2039 *
2040 * Results:
2041 * The return value is a standard Tcl result (errors can occur
2042 * while posting and unposting submenus).
2043 *
2044 * Side effects:
2045 * Menu entries get redisplayed, and the active entry changes.
2046 * Submenus may get posted and unposted.
2047 *
2048 *----------------------------------------------------------------------
2049 */
2050
2051 static int
2052 ActivateMenuEntry (
2053 register Menu *menuPtr, /* Menu in which to activate. */
2054 int index /* Index of entry to activate, or
2055 * -1 to deactivate all entries. */
2056 )
2057 {
2058 register MenuEntry *mePtr;
2059 int result = TCL_OK;
2060
2061 if (menuPtr->active >= 0) {
2062 mePtr = menuPtr->entries[menuPtr->active];
2063
2064 /*
2065 * Don't change the state unless it's currently active (state
2066 * might already have been changed to disabled).
2067 */
2068
2069 if (mePtr->state == tkActiveUid) {
2070 mePtr->state = tkNormalUid;
2071 }
2072 EventuallyRedrawMenu(menuPtr, menuPtr->active);
2073 }
2074 menuPtr->active = index;
2075 if (index >= 0) {
2076 mePtr = menuPtr->entries[index];
2077 mePtr->state = tkActiveUid;
2078 EventuallyRedrawMenu(menuPtr, index);
2079 Tk_Preserve((ClientData) mePtr);
2080 if (mePtr->type == CASCADE_ENTRY) {
2081 result = PostSubmenu(menuPtr->interp, menuPtr, mePtr);
2082 } else {
2083 result = PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL);
2084 }
2085 Tk_Release((ClientData) mePtr);
2086 }
2087 return result;
2088 }
Impressum, Datenschutz