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
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
13 * See the file "license.terms" for information on usage and redistribution
14 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
22 #define DestroyMenu CkDestroyMenu
26 * One of the following data structures is kept for each entry of each
27 * menu managed by this file:
30 typedef struct MenuEntry {
31 int type; /* Type of menu entry; see below for
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
45 * Information related to displaying entry:
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
69 * Information used to implement this entry's action:
72 char *command; /* Command to invoke when entry is invoked.
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).
80 char *offValue; /* Value to store in variable when not
81 * selected (only for check buttons).
85 * Miscellaneous information:
88 int flags; /* Various flags. See below for definitions. */
92 * Flag values defined for menu entries:
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.
100 #define ENTRY_SELECTED 1
101 #define ENTRY_NEEDS_REDISPLAY 4
104 * Types defined for MenuEntries:
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
114 * Mask bits for above types:
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)
126 * Configuration specs for individual menu entries:
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
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
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
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),
174 {CK_CONFIG_STRING, "-onvalue", (char *) NULL, (char *) NULL,
175 DEF_MENU_ENTRY_ON_VALUE, Ck_Offset(MenuEntry, onValue),
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),
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,
210 * A data structure of the following type is kept for each
211 * menu managed by this file:
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
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
228 * Information used when displaying widget:
244 int labelWidth; /* Number of chars to allow for displaying
245 * labels in menu entries. */
246 int indicatorSpace; /* Number of chars for displaying
250 * Miscellaneous information:
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
266 * Flag bits for menus:
268 * REDRAW_PENDING: Non-zero means a DoWhenIdle handler
269 * has already been queued to redraw
271 * RESIZE_PENDING: Non-zero means a call to ComputeMenuGeometry
272 * has already been scheduled.
275 #define REDRAW_PENDING 1
276 #define RESIZE_PENDING 2
279 * Configuration specs valid for the menu as a whole:
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),
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,
355 * Forward declarations for procedures defined later in this file:
358 static int ActivateMenuEntry _ANSI_ARGS_((Menu *menuPtr,
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,
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,
373 static int GetMenuIndex _ANSI_ARGS_((Tcl_Interp *interp,
374 Menu *menuPtr, char *string, int lastOK,
376 static int MenuAddOrInsert _ANSI_ARGS_((Tcl_Interp *interp,
377 Menu *menuPtr, char *indexString, int argc,
379 static void MenuCmdDeletedProc _ANSI_ARGS_((
380 ClientData clientData));
381 static void MenuEventProc _ANSI_ARGS_((ClientData clientData,
383 static MenuEntry * MenuNewEntry _ANSI_ARGS_((Menu *menuPtr, int index,
385 static char * MenuVarProc _ANSI_ARGS_((ClientData clientData,
386 Tcl_Interp *interp, char *name1, char *name2,
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));
394 *--------------------------------------------------------------
398 * This procedure is invoked to process the "menu" Tcl
399 * command. See the user documentation for details on
403 * A standard Tcl result.
406 * See the user documentation.
408 *--------------------------------------------------------------
412 Ck_MenuCmd(clientData, interp, argc, argv)
413 ClientData clientData; /* Main window associated with
415 Tcl_Interp *interp; /* Current interpreter. */
416 int argc; /* Number of arguments. */
417 char **argv; /* Argument strings. */
419 CkWindow *mainPtr = (CkWindow *) clientData;
421 register Menu *menuPtr;
424 Tcl_AppendResult(interp, "wrong # args: should be \"",
425 argv[0], " pathName ?options?\"", (char *) NULL);
430 * Create the new window.
433 new = Ck_CreateWindowFromPath(interp, mainPtr, argv[1], 1);
439 * Initialize the data structure for the menu.
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;
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) {
478 interp->result = menuPtr->winPtr->pathName;
482 Ck_DestroyWindow(menuPtr->winPtr);
487 *--------------------------------------------------------------
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.
496 * A standard Tcl result.
499 * See the user documentation.
501 *--------------------------------------------------------------
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. */
511 register Menu *menuPtr = (Menu *) clientData;
512 register MenuEntry *mePtr;
518 Tcl_AppendResult(interp, "wrong # args: should be \"",
519 argv[0], " option ?arg arg ...?\"", (char *) NULL);
522 Ck_Preserve((ClientData) menuPtr);
524 length = strlen(argv[1]);
525 if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)
530 Tcl_AppendResult(interp, "wrong # args: should be \"",
531 argv[0], " activate index\"", (char *) NULL);
534 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
537 if (menuPtr->active == index) {
541 if ((menuPtr->entries[index]->type == SEPARATOR_ENTRY)
542 || (menuPtr->entries[index]->state == ckDisabledUid)) {
546 result = ActivateMenuEntry(menuPtr, index);
547 } else if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)
550 Tcl_AppendResult(interp, "wrong # args: should be \"",
551 argv[0], " add type ?options?\"", (char *) NULL);
554 if (MenuAddOrInsert(interp, menuPtr, (char *) NULL,
555 argc-2, argv+2) != TCL_OK) {
558 } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
561 Tcl_AppendResult(interp, "wrong # args: should be \"",
562 argv[0], " cget option\"",
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)
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);
577 result = ConfigureMenu(interp, menuPtr, argc-2, argv+2,
578 CK_CONFIG_ARGV_ONLY);
580 } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)) {
581 int first, last, i, numDeleted;
583 if ((argc != 3) && (argc != 4)) {
584 Tcl_AppendResult(interp, "wrong # args: should be \"",
585 argv[0], " delete first ?last?\"", (char *) NULL);
588 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &first) != TCL_OK) {
594 if (GetMenuIndex(interp, menuPtr, argv[3], 0, &last) != TCL_OK) {
598 if ((first < 0) || (last < first)) {
601 numDeleted = last + 1 - first;
602 for (i = first; i <= last; i++) {
603 Ck_EventuallyFree((ClientData) menuPtr->entries[i],
604 (Ck_FreeProc *) DestroyMenuEntry);
606 for (i = last+1; i < menuPtr->numEntries; i++) {
607 menuPtr->entries[i-numDeleted] = menuPtr->entries[i];
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;
615 if (!(menuPtr->flags & RESIZE_PENDING)) {
616 menuPtr->flags |= RESIZE_PENDING;
617 Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
619 } else if ((c == 'e') && (length >= 7)
620 && (strncmp(argv[1], "entrycget", length) == 0)) {
624 Tcl_AppendResult(interp, "wrong # args: should be \"",
625 argv[0], " entrycget index option\"",
629 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
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)) {
645 Tcl_AppendResult(interp, "wrong # args: should be \"",
646 argv[0], " entryconfigure index ?option value ...?\"",
650 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
656 mePtr = menuPtr->entries[index];
657 Ck_Preserve((ClientData) mePtr);
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,
665 (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
667 result = ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc-3,
668 argv+3, CK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
670 Ck_Release((ClientData) mePtr);
671 } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
676 Tcl_AppendResult(interp, "wrong # args: should be \"",
677 argv[0], " index string\"", (char *) NULL);
680 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
684 interp->result = "none";
686 sprintf(interp->result, "%d", index);
688 } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
691 Tcl_AppendResult(interp, "wrong # args: should be \"",
692 argv[0], " insert index type ?options?\"", (char *) NULL);
695 if (MenuAddOrInsert(interp, menuPtr, argv[2],
696 argc-3, argv+3) != TCL_OK) {
699 } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0)
704 Tcl_AppendResult(interp, "wrong # args: should be \"",
705 argv[0], " invoke index\"", (char *) NULL);
708 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
714 mePtr = menuPtr->entries[index];
715 if (mePtr->state == ckDisabledUid) {
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) {
726 if (Tcl_SetVar(interp, mePtr->name, mePtr->onValue,
727 TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
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) {
737 if ((result == TCL_OK) && (mePtr->command != NULL)) {
738 result = CkCopyAndGlobalEval(interp, mePtr->command);
740 if ((result == TCL_OK) && (mePtr->type == CASCADE_ENTRY)) {
741 result = PostSubmenu(menuPtr->interp, menuPtr, mePtr);
743 Ck_Release((ClientData) mePtr);
744 } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0)
749 Tcl_AppendResult(interp, "wrong # args: should be \"",
750 argv[0], " post x y\"", (char *) NULL);
753 if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK)
754 || (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) {
759 * De-activate any active element.
762 ActivateMenuEntry(menuPtr, -1);
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.
770 if (menuPtr->postCommand != NULL) {
771 result = CkCopyAndGlobalEval(menuPtr->interp,
772 menuPtr->postCommand);
773 if (result != TCL_OK) {
776 if (menuPtr->flags & RESIZE_PENDING) {
777 Tk_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
778 ComputeMenuGeometry((ClientData) menuPtr);
781 if (menuPtr->borderPtr != NULL)
783 tmp = menuPtr->winPtr->mainPtr->maxWidth - menuPtr->winPtr->reqWidth;
790 tmp = menuPtr->winPtr->mainPtr->maxHeight - menuPtr->winPtr->reqHeight;
797 if (x != menuPtr->winPtr->x || y != menuPtr->winPtr->y) {
798 Ck_MoveWindow(menuPtr->winPtr, x, y);
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);
805 if (!(menuPtr->winPtr->flags & CK_MAPPED)) {
806 Ck_MapWindow(menuPtr->winPtr);
808 } else if ((c == 'p') && (strncmp(argv[1], "postcascade", length) == 0)
812 Tcl_AppendResult(interp, "wrong # args: should be \"",
813 argv[0], " postcascade index\"", (char *) NULL);
816 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
819 if ((index < 0) || (menuPtr->entries[index]->type != CASCADE_ENTRY)) {
820 result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
822 result = PostSubmenu(interp, menuPtr, menuPtr->entries[index]);
824 } else if ((c == 't') && (strncmp(argv[1], "type", length) == 0)) {
827 Tcl_AppendResult(interp, "wrong # args: should be \"",
828 argv[0], " type index\"", (char *) NULL);
831 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
837 mePtr = menuPtr->entries[index];
838 switch (mePtr->type) {
840 interp->result = "command";
842 case SEPARATOR_ENTRY:
843 interp->result = "separator";
845 case CHECK_BUTTON_ENTRY:
846 interp->result = "checkbutton";
848 case RADIO_BUTTON_ENTRY:
849 interp->result = "radiobutton";
852 interp->result = "cascade";
855 } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) {
857 Tcl_AppendResult(interp, "wrong # args: should be \"",
858 argv[0], " unpost\"", (char *) NULL);
861 Ck_UnmapWindow(menuPtr->winPtr);
862 if (result == TCL_OK) {
863 result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
865 } else if ((c == 'y') && (strncmp(argv[1], "yposition", length) == 0)) {
869 Tcl_AppendResult(interp, "wrong # args: should be \"",
870 argv[0], " yposition index\"", (char *) NULL);
873 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
877 interp->result = "0";
879 sprintf(interp->result, "%d", menuPtr->entries[index]->y);
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",
890 Ck_Release((ClientData) menuPtr);
894 Ck_Release((ClientData) menuPtr);
899 *----------------------------------------------------------------------
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).
911 * Everything associated with the menu is freed up.
913 *----------------------------------------------------------------------
917 DestroyMenu(clientData)
918 ClientData clientData; /* Info about menu widget. */
920 register Menu *menuPtr = (Menu *) clientData;
924 * Free up all the stuff that requires special handling, then
925 * let Ck_FreeOptions handle all the standard option-related
929 for (i = 0; i < menuPtr->numEntries; i++) {
930 DestroyMenuEntry((ClientData) menuPtr->entries[i]);
932 if (menuPtr->entries != NULL) {
933 ckfree((char *) menuPtr->entries);
935 Ck_FreeOptions(configSpecs, (char *) menuPtr, 0);
936 ckfree((char *) menuPtr);
940 *----------------------------------------------------------------------
942 * DestroyMenuEntry --
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).
952 * Everything associated with the menu entry is freed up.
954 *----------------------------------------------------------------------
958 DestroyMenuEntry(clientData)
959 ClientData clientData; /* Pointer to entry to be freed. */
961 register MenuEntry *mePtr = (MenuEntry *) clientData;
962 Menu *menuPtr = mePtr->menuPtr;
965 * Free up all the stuff that requires special handling, then
966 * let Ck_FreeOptions handle all the standard option-related
970 if (menuPtr->postedCascade == mePtr) {
972 * Ignore errors while unposting the menu, since it's possible
973 * that the menu has already been deleted and the unpost will
977 PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL);
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);
984 Ck_FreeOptions(entryConfigSpecs, (char *) mePtr,
985 (COMMAND_MASK << mePtr->type));
986 ckfree((char *) mePtr);
990 *----------------------------------------------------------------------
994 * This procedure is called to process an argv/argc list, plus
995 * the option database, in order to configure (or reconfigure)
999 * The return value is a standard Tcl result. If TCL_ERROR is
1000 * returned, then interp->result contains an error message.
1003 * Configuration information, such as colors, font, etc. get set
1004 * for menuPtr; old resources get freed, if there were any.
1006 *----------------------------------------------------------------------
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. */
1020 if (Ck_ConfigureWidget(interp, menuPtr->winPtr, configSpecs,
1021 argc, argv, (char *) menuPtr, flags) != TCL_OK) {
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
1032 for (i = 0; i < menuPtr->numEntries; i++) {
1035 mePtr = menuPtr->entries[i];
1036 ConfigureMenuEntry(interp, menuPtr, mePtr, i, 0, (char **) NULL,
1037 CK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
1040 Ck_SetInternalBorder(menuPtr->winPtr, menuPtr->borderPtr != NULL);
1042 if (!(menuPtr->flags & RESIZE_PENDING)) {
1043 menuPtr->flags |= RESIZE_PENDING;
1044 Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
1050 *----------------------------------------------------------------------
1052 * ConfigureMenuEntry --
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.
1059 * The return value is a standard Tcl result. If TCL_ERROR is
1060 * returned, then interp->result contains an error message.
1063 * Configuration information such as label and accelerator get
1064 * set for mePtr; old resources get freed, if there were any.
1066 *----------------------------------------------------------------------
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
1076 int index; /* Index of mePtr within menuPtr's
1078 int argc; /* Number of valid entries in argv. */
1079 char **argv; /* Arguments. */
1080 int flags; /* Additional flags to pass to
1081 * Tk_ConfigureWidget. */
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
1090 if (menuPtr->postedCascade == mePtr) {
1091 if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL)
1093 Tk_BackgroundError(menuPtr->interp);
1098 * If this entry is a check button or radio button, then remove
1099 * its old trace procedure.
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);
1110 if (Ck_ConfigureWidget(interp, menuPtr->winPtr, entryConfigSpecs,
1111 argc, argv, (char *) mePtr,
1112 flags | (COMMAND_MASK << mePtr->type)) != TCL_OK) {
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.
1122 if (mePtr->label == NULL) {
1123 mePtr->labelLength = 0;
1125 mePtr->labelLength = strlen(mePtr->label);
1127 if (mePtr->accel == NULL) {
1128 mePtr->accelLength = 0;
1130 mePtr->accelLength = strlen(mePtr->accel);
1133 if (mePtr->state == ckActiveUid) {
1134 if (index != menuPtr->active) {
1135 ActivateMenuEntry(menuPtr, index);
1138 if (index == menuPtr->active) {
1139 ActivateMenuEntry(menuPtr, -1);
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;
1149 if ((mePtr->type == CHECK_BUTTON_ENTRY)
1150 || (mePtr->type == RADIO_BUTTON_ENTRY)) {
1153 if (mePtr->name == NULL) {
1154 mePtr->name = (char *) ckalloc(mePtr->labelLength + 1);
1155 strcpy(mePtr->name, (mePtr->label == NULL) ? "" : mePtr->label);
1157 if (mePtr->onValue == NULL) {
1158 mePtr->onValue = (char *) ckalloc(mePtr->labelLength + 1);
1159 strcpy(mePtr->onValue, (mePtr->label == NULL) ? "" : mePtr->label);
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.
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;
1176 Tcl_SetVar(interp, mePtr->name,
1177 (mePtr->type == CHECK_BUTTON_ENTRY) ? mePtr->offValue : "",
1180 Tcl_TraceVar(interp, mePtr->name,
1181 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1182 MenuVarProc, (ClientData) mePtr);
1185 if (!(menuPtr->flags & RESIZE_PENDING)) {
1186 menuPtr->flags |= RESIZE_PENDING;
1187 Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
1193 *--------------------------------------------------------------
1195 * ComputeMenuGeometry --
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
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.
1210 *--------------------------------------------------------------
1214 ComputeMenuGeometry(clientData)
1215 ClientData clientData; /* Structure describing menu. */
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;
1224 if (menuPtr->winPtr == NULL) {
1228 maxLabelWidth = maxIndicatorWidth = maxAccelWidth = 0;
1231 for (i = 0; i < menuPtr->numEntries; i++) {
1232 mePtr = menuPtr->entries[i];
1235 if (mePtr->label != NULL) {
1236 CkMeasureChars(winPtr->mainPtr,
1237 mePtr->label, mePtr->labelLength, 0,
1238 100000, 0, CK_NEWLINES_NOT_SPECIAL, &width, &dummy);
1242 if (mePtr->indicatorOn && (mePtr->type == CHECK_BUTTON_ENTRY ||
1243 mePtr->type == RADIO_BUTTON_ENTRY)) {
1246 if (width > maxLabelWidth) {
1247 maxLabelWidth = width;
1249 if (mePtr->type == CASCADE_ENTRY) {
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);
1258 if (width > maxAccelWidth) {
1259 maxAccelWidth = width;
1261 if (indicatorSpace > maxIndicatorWidth) {
1262 maxIndicatorWidth = indicatorSpace;
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).
1276 menuPtr->indicatorSpace = maxIndicatorWidth;
1277 menuPtr->labelWidth = maxLabelWidth;
1278 width = menuPtr->indicatorSpace + menuPtr->labelWidth + maxAccelWidth;
1288 if (menuPtr->borderPtr != NULL) {
1292 if (width != menuPtr->winPtr->reqWidth ||
1293 height != menuPtr->winPtr->reqHeight) {
1294 Ck_GeometryRequest(menuPtr->winPtr, width, height);
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.
1303 EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
1305 menuPtr->flags &= ~RESIZE_PENDING;
1309 *----------------------------------------------------------------------
1313 * This procedure is invoked to display a menu widget.
1319 * Commands are output to X to display the menu in its
1322 *----------------------------------------------------------------------
1326 DisplayMenu(clientData)
1327 ClientData clientData; /* Information about widget. */
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;
1337 menuPtr->flags &= ~REDRAW_PENDING;
1338 if (menuPtr->winPtr == NULL || !(winPtr->flags & CK_MAPPED))
1341 x = cursorX = menuPtr->borderPtr != NULL ? 1 : 0;
1342 y = cursorY = menuPtr->borderPtr != NULL ? 1 : 0;
1345 * Loop through all of the entries, drawing them one at a time.
1348 leftEdge = menuPtr->indicatorSpace + x;
1350 for (index = 0; index < menuPtr->numEntries; index++, y++) {
1351 mePtr = menuPtr->entries[index];
1352 if (mePtr->state == ckActiveUid) {
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)
1360 if (!(mePtr->flags & ENTRY_NEEDS_REDISPLAY)) {
1363 mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY;
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;
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;
1385 bg = nBg; fg = nFg; attr = nAt;
1388 Ck_SetWindowAttr(winPtr, fg, bg, attr);
1389 Ck_ClearToEol(winPtr, x, y);
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);
1410 * Draw accelerator or cascade arrow.
1413 if (mePtr->type == CASCADE_ENTRY) {
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);
1427 * Draw check-button/radio-button indicators.
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 ?
1436 if (mePtr->flags & ENTRY_SELECTED) {
1439 Ck_GetGChar(menuPtr->interp,
1440 mePtr->type == CHECK_BUTTON_ENTRY ? "diamond" : "bullet",
1442 Ck_SetWindowAttr(winPtr, mePtr->indicatorFg < 0 ?
1443 menuPtr->indicatorFg : mePtr->indicatorFg, nBg, nAt);
1444 mvwaddch(winPtr->window, y, x + 1, gchar);
1452 if (mePtr->type == SEPARATOR_ENTRY) {
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);
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);
1468 wmove(winPtr->window, cursorY, cursorX);
1469 Ck_EventuallyRefresh(winPtr);
1473 *--------------------------------------------------------------
1477 * Parse a textual index into a menu and return the numerical
1478 * index of the indicated entry.
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.
1489 *--------------------------------------------------------------
1493 GetMenuIndex(interp, menuPtr, string, lastOK, indexPtr)
1494 Tcl_Interp *interp; /* For error messages. */
1495 Menu *menuPtr; /* Menu for which the index is being
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. */
1505 if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
1506 *indexPtr = menuPtr->active;
1510 if (((string[0] == 'l') && (strcmp(string, "last") == 0))
1511 || ((string[0] == 'e') && (strcmp(string, "end") == 0))) {
1512 *indexPtr = menuPtr->numEntries - ((lastOK) ? 0 : 1);
1516 if ((string[0] == 'n') && (strcmp(string, "none") == 0)) {
1521 if (string[0] == '@') {
1522 if (Tcl_GetInt(interp, string+1, &i) == TCL_OK) {
1523 if (menuPtr->borderPtr != NULL)
1525 if (i >= menuPtr->numEntries)
1532 Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
1536 if (isdigit((unsigned char) string[0])) {
1537 if (Tcl_GetInt(interp, string, &i) == TCL_OK) {
1538 if (i >= menuPtr->numEntries) {
1540 i = menuPtr->numEntries;
1542 i = menuPtr->numEntries-1;
1550 Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
1553 for (i = 0; i < menuPtr->numEntries; i++) {
1556 label = menuPtr->entries[i]->label;
1558 && (Tcl_StringMatch(menuPtr->entries[i]->label, string))) {
1564 Tcl_AppendResult(interp, "bad menu entry index \"",
1565 string, "\"", (char *) NULL);
1570 *--------------------------------------------------------------
1574 * This procedure is invoked by the Tk dispatcher for various
1581 * When the window gets deleted, internal structures get
1582 * cleaned up. When it gets exposed, it is redisplayed.
1584 *--------------------------------------------------------------
1588 MenuEventProc(clientData, eventPtr)
1589 ClientData clientData; /* Information about window. */
1590 CkEvent *eventPtr; /* Information about event. */
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));
1601 if (menuPtr->flags & REDRAW_PENDING) {
1602 Tk_CancelIdleCall(DisplayMenu, (ClientData) menuPtr);
1604 if (menuPtr->flags & RESIZE_PENDING) {
1605 Tk_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
1607 Ck_EventuallyFree((ClientData) menuPtr, (Ck_FreeProc *) DestroyMenu);
1612 *----------------------------------------------------------------------
1614 * MenuCmdDeletedProc --
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.
1624 * The widget is destroyed.
1626 *----------------------------------------------------------------------
1630 MenuCmdDeletedProc(clientData)
1631 ClientData clientData; /* Pointer to widget record for widget. */
1633 Menu *menuPtr = (Menu *) clientData;
1634 CkWindow *winPtr = menuPtr->winPtr;
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.
1643 if (winPtr != NULL) {
1644 menuPtr->winPtr = NULL;
1645 Ck_DestroyWindow(winPtr);
1650 *----------------------------------------------------------------------
1654 * This procedure allocates and initializes a new menu entry.
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.
1662 * Storage gets allocated.
1664 *----------------------------------------------------------------------
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
1672 int type; /* The type of the new entry. */
1675 MenuEntry **newEntries;
1679 * Create a new array of entries with an empty slot for the
1683 newEntries = (MenuEntry **) ckalloc((unsigned)
1684 ((menuPtr->numEntries+1)*sizeof(MenuEntry *)));
1685 for (i = 0; i < index; i++) {
1686 newEntries[i] = menuPtr->entries[i];
1688 for ( ; i < menuPtr->numEntries; i++) {
1689 newEntries[i+1] = menuPtr->entries[i];
1691 if (menuPtr->numEntries != 0) {
1692 ckfree((char *) menuPtr->entries);
1694 menuPtr->entries = newEntries;
1695 menuPtr->numEntries++;
1696 menuPtr->entries[index] = mePtr = (MenuEntry *) ckalloc(sizeof(MenuEntry));
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;
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;
1721 mePtr->onValue = NULL;
1722 mePtr->offValue = NULL;
1728 *----------------------------------------------------------------------
1730 * MenuAddOrInsert --
1732 * This procedure does all of the work of the "add" and "insert"
1733 * widget commands, allowing the code for these to be shared.
1736 * A standard Tcl return value.
1739 * A new menu entry is created in menuPtr.
1741 *----------------------------------------------------------------------
1745 MenuAddOrInsert(interp, menuPtr, indexString, argc, argv)
1746 Tcl_Interp *interp; /* Used for error reporting. */
1747 Menu *menuPtr; /* Widget in which to create new
1749 char *indexString; /* String describing index at which
1750 * to insert. NULL means insert at
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. */
1757 int c, type, i, index;
1761 if (indexString != NULL) {
1762 if (GetMenuIndex(interp, menuPtr, indexString, 1, &index) != TCL_OK) {
1766 index = menuPtr->numEntries;
1769 Tcl_AppendResult(interp, "bad index \"", indexString, "\"",
1775 * Figure out the type of the new entry.
1779 length = strlen(argv[0]);
1780 if ((c == 'c') && (strncmp(argv[0], "cascade", length) == 0)
1782 type = CASCADE_ENTRY;
1783 } else if ((c == 'c') && (strncmp(argv[0], "checkbutton", length) == 0)
1785 type = CHECK_BUTTON_ENTRY;
1786 } else if ((c == 'c') && (strncmp(argv[0], "command", length) == 0)
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;
1796 Tcl_AppendResult(interp, "bad menu entry type \"",
1797 argv[0], "\": must be cascade, checkbutton, ",
1798 "command, radiobutton, or separator", (char *) NULL);
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];
1808 menuPtr->numEntries--;
1815 *--------------------------------------------------------------
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.
1825 * NULL is always returned.
1828 * The menu entry may become selected or deselected.
1830 *--------------------------------------------------------------
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. */
1841 MenuEntry *mePtr = (MenuEntry *) clientData;
1845 menuPtr = mePtr->menuPtr;
1848 * If the variable is being unset, then re-establish the
1849 * trace unless the whole interpreter is going away.
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);
1859 EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
1860 return (char *) NULL;
1864 * Use the value of the variable to update the selected status of
1868 value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY);
1869 if (value == NULL) {
1872 if (strcmp(value, mePtr->onValue) == 0) {
1873 if (mePtr->flags & ENTRY_SELECTED) {
1874 return (char *) NULL;
1876 mePtr->flags |= ENTRY_SELECTED;
1877 } else if (mePtr->flags & ENTRY_SELECTED) {
1878 mePtr->flags &= ~ENTRY_SELECTED;
1880 return (char *) NULL;
1882 EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
1883 return (char *) NULL;
1887 *----------------------------------------------------------------------
1889 * EventuallyRedrawMenu --
1891 * Arrange for an entry of a menu, or the whole menu, to be
1892 * redisplayed at some point in the future.
1898 * A when-idle hander is scheduled to do the redisplay, if there
1899 * isn't one already scheduled.
1901 *----------------------------------------------------------------------
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. */
1912 if (menuPtr->winPtr == NULL) {
1915 if (mePtr != NULL) {
1916 mePtr->flags |= ENTRY_NEEDS_REDISPLAY;
1918 for (i = 0; i < menuPtr->numEntries; i++) {
1919 menuPtr->entries[i]->flags |= ENTRY_NEEDS_REDISPLAY;
1922 if ((menuPtr->winPtr == NULL) || !(menuPtr->winPtr->flags & CK_MAPPED)
1923 || (menuPtr->flags & REDRAW_PENDING)) {
1926 Tk_DoWhenIdle(DisplayMenu, (ClientData) menuPtr);
1927 menuPtr->flags |= REDRAW_PENDING;
1931 *--------------------------------------------------------------
1935 * This procedure arranges for a particular submenu (i.e. the
1936 * menu corresponding to a given cascade entry) to be
1940 * A standard Tcl return result. Errors may occur in the
1941 * Tcl commands generated to post and unpost submenus.
1944 * If there is already a submenu posted, it is unposted.
1945 * The new submenu is then posted.
1947 *--------------------------------------------------------------
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. */
1963 if (mePtr == menuPtr->postedCascade) {
1967 if (menuPtr->postedCascade != NULL) {
1969 * Note: when unposting a submenu, we have to redraw the entire
1970 * parent menu. This is because of a combination of the following
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
1981 * The simplest solution is just force a complete redisplay of
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) {
1994 if ((mePtr != NULL) && (mePtr->name != NULL)
1995 && (menuPtr->winPtr->flags & CK_MAPPED)) {
1997 * Make sure that the cascaded submenu is a child of the
2001 winPtr = Ck_NameToWindow(interp, mePtr->name, menuPtr->winPtr);
2002 if (winPtr == NULL) {
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);
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).
2017 x = menuPtr->winPtr->x;
2018 y = menuPtr->winPtr->y;
2019 x += menuPtr->winPtr->width;
2021 sprintf(string, "%d %d", x, y);
2022 result = Tcl_VarEval(interp, mePtr->name, " post ", string,
2024 if (result != TCL_OK) {
2027 menuPtr->postedCascade = mePtr;
2033 *----------------------------------------------------------------------
2035 * ActivateMenuEntry --
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.
2042 * The return value is a standard Tcl result (errors can occur
2043 * while posting and unposting submenus).
2046 * Menu entries get redisplayed, and the active entry changes.
2047 * Submenus may get posted and unposted.
2049 *----------------------------------------------------------------------
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. */
2058 register MenuEntry *mePtr;
2059 int result = TCL_OK;
2061 if (menuPtr->active >= 0) {
2062 mePtr = menuPtr->entries[menuPtr->active];
2065 * Don't change the state unless it's currently active (state
2066 * might already have been changed to disabled).
2069 if (mePtr->state == ckActiveUid) {
2070 mePtr->state = ckNormalUid;
2072 EventuallyRedrawMenu(menuPtr, menuPtr->entries[menuPtr->active]);
2074 menuPtr->active = index;
2076 mePtr = menuPtr->entries[index];
2077 mePtr->state = ckActiveUid;
2078 EventuallyRedrawMenu(menuPtr, mePtr);