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