1 /*
2 * tkMenu.c --
3 *
4 * This module implements menus for the Tk toolkit. The menus
5 * support normal button entries, plus check buttons, radio
6 * buttons, iconic forms of all of the above, and separator
7 * entries.
8 *
9 * Copyright (c) 1990-1994 The Regents of the University of California.
10 * Copyright (c) 1994-1996 Sun Microsystems, Inc.
11 *
12 * See the file "license.terms" for information on usage and redistribution
13 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14 *
15 * SCCS: @(#) tkMenu.c 1.105 96/12/17 11:16:18
16 */
17
18 #include "tkInt.h"
19 #include "tkDefault.h"
20 #ifdef MAC_TCL
21 # include "tkMacInt.h"
22 #endif
23
24 /*
25 * One of the following data structures is kept for each entry of each
26 * menu managed by this file:
27 */
28
29 typedef struct MenuEntry {
30 int type; /* Type of menu entry; see below for
31 * valid types. */
32 struct Menu *menuPtr; /* Menu with which this entry is associated. */
33 char *label; /* Main text label displayed in entry (NULL
34 * if no label). Malloc'ed. */
35 int labelLength; /* Number of non-NULL characters in label. */
36 int underline; /* Index of character to underline. */
37 Pixmap bitmap; /* Bitmap to display in menu entry, or None.
38 * If not None then label is ignored. */
39 char *imageString; /* Name of image to display (malloc'ed), or
40 * NULL. If non-NULL, bitmap, text, and
41 * textVarName are ignored. */
42 Tk_Image image; /* Image to display in menu entry, or NULL if
43 * none. */
44 char *selectImageString; /* Name of image to display when selected
45 * (malloc'ed), or NULL. */
46 Tk_Image selectImage; /* Image to display in entry when selected,
47 * or NULL if none. Ignored if image is
48 * NULL. */
49 char *accel; /* Accelerator string displayed at right
50 * of menu entry. NULL means no such
51 * accelerator. Malloc'ed. */
52 int accelLength; /* Number of non-NULL characters in
53 * accelerator. */
54
55 /*
56 * Information related to displaying entry:
57 */
58
59 Tk_Uid state; /* State of button for display purposes:
60 * normal, active, or disabled. */
61 int height; /* Number of pixels occupied by entry in
62 * vertical dimension, including raised
63 * border drawn around entry when active. */
64 int y; /* Y-coordinate of topmost pixel in entry. */
65 int indicatorOn; /* True means draw indicator, false means
66 * don't draw it. */
67 int indicatorDiameter; /* Size of indicator display, in pixels. */
68 Tk_3DBorder border; /* Structure used to draw background for
69 * entry. NULL means use overall border
70 * for menu. */
71 XColor *fg; /* Foreground color to use for entry. NULL
72 * means use foreground color from menu. */
73 Tk_3DBorder activeBorder; /* Used to draw background and border when
74 * element is active. NULL means use
75 * activeBorder from menu. */
76 XColor *activeFg; /* Foreground color to use when entry is
77 * active. NULL means use active foreground
78 * from menu. */
79 XFontStruct *fontPtr; /* Text font for menu entries. NULL means
80 * use overall font for menu. */
81 GC textGC; /* GC for drawing text in entry. NULL means
82 * use overall textGC for menu. */
83 GC activeGC; /* GC for drawing text in entry when active.
84 * NULL means use overall activeGC for
85 * menu. */
86 GC disabledGC; /* Used to produce disabled effect for entry.
87 * NULL means use overall disabledGC from
88 * menu structure. See comments for
89 * disabledFg in menu structure for more
90 * information. */
91 XColor *indicatorFg; /* Color for indicators in radio and check
92 * button entries. NULL means use indicatorFg
93 * GC from menu. */
94 GC indicatorGC; /* For drawing indicators. None means use
95 * GC from menu. */
96
97 /*
98 * Information used to implement this entry's action:
99 */
100
101 char *command; /* Command to invoke when entry is invoked.
102 * Malloc'ed. */
103 char *name; /* Name of variable (for check buttons and
104 * radio buttons) or menu (for cascade
105 * entries). Malloc'ed.*/
106 char *onValue; /* Value to store in variable when selected
107 * (only for radio and check buttons).
108 * Malloc'ed. */
109 char *offValue; /* Value to store in variable when not
110 * selected (only for check buttons).
111 * Malloc'ed. */
112
113 /*
114 * Miscellaneous information:
115 */
116
117 int flags; /* Various flags. See below for definitions. */
118 } MenuEntry;
119
120 /*
121 * Flag values defined for menu entries:
122 *
123 * ENTRY_SELECTED: Non-zero means this is a radio or check
124 * button and that it should be drawn in
125 * the "selected" state.
126 * ENTRY_NEEDS_REDISPLAY: Non-zero means the entry should be redisplayed.
127 */
128
129 #define ENTRY_SELECTED 1
130 #define ENTRY_NEEDS_REDISPLAY 4
131
132 /*
133 * Types defined for MenuEntries:
134 */
135
136 #define COMMAND_ENTRY 0
137 #define SEPARATOR_ENTRY 1
138 #define CHECK_BUTTON_ENTRY 2
139 #define RADIO_BUTTON_ENTRY 3
140 #define CASCADE_ENTRY 4
141 #define TEAROFF_ENTRY 5
142
143 /*
144 * Mask bits for above types:
145 */
146
147 #define COMMAND_MASK TK_CONFIG_USER_BIT
148 #define SEPARATOR_MASK (TK_CONFIG_USER_BIT << 1)
149 #define CHECK_BUTTON_MASK (TK_CONFIG_USER_BIT << 2)
150 #define RADIO_BUTTON_MASK (TK_CONFIG_USER_BIT << 3)
151 #define CASCADE_MASK (TK_CONFIG_USER_BIT << 4)
152 #define TEAROFF_MASK (TK_CONFIG_USER_BIT << 5)
153 #define ALL_MASK (COMMAND_MASK | SEPARATOR_MASK \
154 | CHECK_BUTTON_MASK | RADIO_BUTTON_MASK | CASCADE_MASK | TEAROFF_MASK)
155
156 /*
157 * Configuration specs for individual menu entries:
158 */
159
160 static Tk_ConfigSpec entryConfigSpecs[] = {
161 {TK_CONFIG_BORDER, "-activebackground", (char *) NULL, (char *) NULL,
162 DEF_MENU_ENTRY_ACTIVE_BG, Tk_Offset(MenuEntry, activeBorder),
163 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
164 |TK_CONFIG_NULL_OK},
165 {TK_CONFIG_COLOR, "-activeforeground", (char *) NULL, (char *) NULL,
166 DEF_MENU_ENTRY_ACTIVE_FG, Tk_Offset(MenuEntry, activeFg),
167 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
168 |TK_CONFIG_NULL_OK},
169 {TK_CONFIG_STRING, "-accelerator", (char *) NULL, (char *) NULL,
170 DEF_MENU_ENTRY_ACCELERATOR, Tk_Offset(MenuEntry, accel),
171 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
172 |TK_CONFIG_NULL_OK},
173 {TK_CONFIG_BORDER, "-background", (char *) NULL, (char *) NULL,
174 DEF_MENU_ENTRY_BG, Tk_Offset(MenuEntry, border),
175 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
176 |SEPARATOR_MASK|TEAROFF_MASK|TK_CONFIG_NULL_OK},
177 {TK_CONFIG_BITMAP, "-bitmap", (char *) NULL, (char *) NULL,
178 DEF_MENU_ENTRY_BITMAP, Tk_Offset(MenuEntry, bitmap),
179 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
180 |TK_CONFIG_NULL_OK},
181 {TK_CONFIG_STRING, "-command", (char *) NULL, (char *) NULL,
182 DEF_MENU_ENTRY_COMMAND, Tk_Offset(MenuEntry, command),
183 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
184 |TK_CONFIG_NULL_OK},
185 {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
186 DEF_MENU_ENTRY_FONT, Tk_Offset(MenuEntry, fontPtr),
187 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
188 |TK_CONFIG_NULL_OK},
189 {TK_CONFIG_COLOR, "-foreground", (char *) NULL, (char *) NULL,
190 DEF_MENU_ENTRY_FG, Tk_Offset(MenuEntry, fg),
191 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
192 |TK_CONFIG_NULL_OK},
193 {TK_CONFIG_STRING, "-image", (char *) NULL, (char *) NULL,
194 DEF_MENU_ENTRY_IMAGE, Tk_Offset(MenuEntry, imageString),
195 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
196 |TK_CONFIG_NULL_OK},
197 {TK_CONFIG_BOOLEAN, "-indicatoron", (char *) NULL, (char *) NULL,
198 DEF_MENU_ENTRY_INDICATOR, Tk_Offset(MenuEntry, indicatorOn),
199 CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_DONT_SET_DEFAULT},
200 {TK_CONFIG_STRING, "-label", (char *) NULL, (char *) NULL,
201 DEF_MENU_ENTRY_LABEL, Tk_Offset(MenuEntry, label),
202 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK},
203 {TK_CONFIG_STRING, "-menu", (char *) NULL, (char *) NULL,
204 DEF_MENU_ENTRY_MENU, Tk_Offset(MenuEntry, name),
205 CASCADE_MASK|TK_CONFIG_NULL_OK},
206 {TK_CONFIG_STRING, "-offvalue", (char *) NULL, (char *) NULL,
207 DEF_MENU_ENTRY_OFF_VALUE, Tk_Offset(MenuEntry, offValue),
208 CHECK_BUTTON_MASK},
209 {TK_CONFIG_STRING, "-onvalue", (char *) NULL, (char *) NULL,
210 DEF_MENU_ENTRY_ON_VALUE, Tk_Offset(MenuEntry, onValue),
211 CHECK_BUTTON_MASK},
212 {TK_CONFIG_COLOR, "-selectcolor", (char *) NULL, (char *) NULL,
213 DEF_MENU_ENTRY_SELECT, Tk_Offset(MenuEntry, indicatorFg),
214 CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
215 {TK_CONFIG_STRING, "-selectimage", (char *) NULL, (char *) NULL,
216 DEF_MENU_ENTRY_SELECT_IMAGE, Tk_Offset(MenuEntry, selectImageString),
217 CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
218 {TK_CONFIG_UID, "-state", (char *) NULL, (char *) NULL,
219 DEF_MENU_ENTRY_STATE, Tk_Offset(MenuEntry, state),
220 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
221 |TEAROFF_MASK|TK_CONFIG_DONT_SET_DEFAULT},
222 {TK_CONFIG_STRING, "-value", (char *) NULL, (char *) NULL,
223 DEF_MENU_ENTRY_VALUE, Tk_Offset(MenuEntry, onValue),
224 RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
225 {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
226 DEF_MENU_ENTRY_CHECK_VARIABLE, Tk_Offset(MenuEntry, name),
227 CHECK_BUTTON_MASK|TK_CONFIG_NULL_OK},
228 {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
229 DEF_MENU_ENTRY_RADIO_VARIABLE, Tk_Offset(MenuEntry, name),
230 RADIO_BUTTON_MASK},
231 {TK_CONFIG_INT, "-underline", (char *) NULL, (char *) NULL,
232 DEF_MENU_ENTRY_UNDERLINE, Tk_Offset(MenuEntry, underline),
233 COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
234 |TK_CONFIG_DONT_SET_DEFAULT},
235 {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
236 (char *) NULL, 0, 0}
237 };
238
239 /*
240 * A data structure of the following type is kept for each
241 * menu managed by this file:
242 */
243
244 typedef struct Menu {
245 Tk_Window tkwin; /* Window that embodies the pane. NULL
246 * means that the window has been destroyed
247 * but the data structures haven't yet been
248 * cleaned up.*/
249 Display *display; /* Display containing widget. Needed, among
250 * other things, so that resources can be
251 * freed up even after tkwin has gone away. */
252 Tcl_Interp *interp; /* Interpreter associated with menu. */
253 Tcl_Command widgetCmd; /* Token for menu's widget command. */
254 MenuEntry **entries; /* Array of pointers to all the entries
255 * in the menu. NULL means no entries. */
256 int numEntries; /* Number of elements in entries. */
257 int active; /* Index of active entry. -1 means
258 * nothing active. */
259
260 /*
261 * Information used when displaying widget:
262 */
263
264 Tk_3DBorder border; /* Structure used to draw 3-D
265 * border and background for menu. */
266 int borderWidth; /* Width of border around whole menu. */
267 Tk_3DBorder activeBorder; /* Used to draw background and border for
268 * active element (if any). */
269 int activeBorderWidth; /* Width of border around active element. */
270 int relief; /* 3-d effect: TK_RELIEF_RAISED, etc. */
271 XFontStruct *fontPtr; /* Text font for menu entries. */
272 XColor *fg; /* Foreground color for entries. */
273 GC textGC; /* GC for drawing text and other features
274 * of menu entries. */
275 XColor *disabledFg; /* Foreground color when disabled. NULL
276 * means use normalFg with a 50% stipple
277 * instead. */
278 Pixmap gray; /* Bitmap for drawing disabled entries in
279 * a stippled fashion. None means not
280 * allocated yet. */
281 GC disabledGC; /* Used to produce disabled effect. If
282 * disabledFg isn't NULL, this GC is used to
283 * draw text and icons for disabled entries.
284 * Otherwise text and icons are drawn with
285 * normalGC and this GC is used to stipple
286 * background across them. */
287 XColor *activeFg; /* Foreground color for active entry. */
288 GC activeGC; /* GC for drawing active entry. */
289 XColor *indicatorFg; /* Color for indicators in radio and check
290 * button entries. */
291 GC indicatorGC; /* For drawing indicators. */
292 int indicatorSpace; /* Number of pixels to allow for displaying
293 * indicators in menu entries (includes extra
294 * space around indicator). */
295 int labelWidth; /* Number of pixels to allow for displaying
296 * labels in menu entries. */
297
298 /*
299 * Miscellaneous information:
300 */
301
302 int tearOff; /* 1 means this is a tear-off menu, so the
303 * first entry always shows a dashed stripe
304 * for tearing off. */
305 char *tearOffCommand; /* If non-NULL, points to a command to
306 * run whenever the menu is torn-off. */
307 int transient; /* 1 means menu is only posted briefly as
308 * a popup or pulldown or cascade. 0 means
309 * menu is always visible, e.g. as a torn-off
310 * menu. Determines whether save_under and
311 * override_redirect should be set. */
312 Tk_Cursor cursor; /* Current cursor for window, or None. */
313 char *takeFocus; /* Value of -takefocus option; not used in
314 * the C code, but used by keyboard traversal
315 * scripts. Malloc'ed, but may be NULL. */
316 char *postCommand; /* Command to execute just before posting
317 * this menu, or NULL. Malloc-ed. */
318 MenuEntry *postedCascade; /* Points to menu entry for cascaded
319 * submenu that is currently posted, or
320 * NULL if no submenu posted. */
321 int flags; /* Various flags; see below for
322 * definitions. */
323 } Menu;
324
325 /*
326 * Flag bits for menus:
327 *
328 * REDRAW_PENDING: Non-zero means a DoWhenIdle handler
329 * has already been queued to redraw
330 * this window.
331 * RESIZE_PENDING: Non-zero means a call to ComputeMenuGeometry
332 * has already been scheduled.
333 */
334
335 #define REDRAW_PENDING 1
336 #define RESIZE_PENDING 2
337
338 /*
339 * Configuration specs valid for the menu as a whole:
340 */
341
342 static Tk_ConfigSpec configSpecs[] = {
343 {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
344 DEF_MENU_ACTIVE_BG_COLOR, Tk_Offset(Menu, activeBorder),
345 TK_CONFIG_COLOR_ONLY},
346 {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
347 DEF_MENU_ACTIVE_BG_MONO, Tk_Offset(Menu, activeBorder),
348 TK_CONFIG_MONO_ONLY},
349 {TK_CONFIG_PIXELS, "-activeborderwidth", "activeBorderWidth", "BorderWidth",
350 DEF_MENU_ACTIVE_BORDER_WIDTH, Tk_Offset(Menu, activeBorderWidth), 0},
351 {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
352 DEF_MENU_ACTIVE_FG_COLOR, Tk_Offset(Menu, activeFg),
353 TK_CONFIG_COLOR_ONLY},
354 {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
355 DEF_MENU_ACTIVE_FG_MONO, Tk_Offset(Menu, activeFg),
356 TK_CONFIG_MONO_ONLY},
357 {TK_CONFIG_BORDER, "-background", "background", "Background",
358 DEF_MENU_BG_COLOR, Tk_Offset(Menu, border), TK_CONFIG_COLOR_ONLY},
359 {TK_CONFIG_BORDER, "-background", "background", "Background",
360 DEF_MENU_BG_MONO, Tk_Offset(Menu, border), TK_CONFIG_MONO_ONLY},
361 {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
362 (char *) NULL, 0, 0},
363 {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
364 (char *) NULL, 0, 0},
365 {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
366 DEF_MENU_BORDER_WIDTH, Tk_Offset(Menu, borderWidth), 0},
367 {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
368 DEF_MENU_CURSOR, Tk_Offset(Menu, cursor), TK_CONFIG_NULL_OK},
369 {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
370 "DisabledForeground", DEF_MENU_DISABLED_FG_COLOR,
371 Tk_Offset(Menu, disabledFg), TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
372 {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
373 "DisabledForeground", DEF_MENU_DISABLED_FG_MONO,
374 Tk_Offset(Menu, disabledFg), TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
375 {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
376 (char *) NULL, 0, 0},
377 {TK_CONFIG_FONT, "-font", "font", "Font",
378 DEF_MENU_FONT, Tk_Offset(Menu, fontPtr), 0},
379 {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
380 DEF_MENU_FG, Tk_Offset(Menu, fg), 0},
381 {TK_CONFIG_STRING, "-postcommand", "postCommand", "Command",
382 DEF_MENU_POST_COMMAND, Tk_Offset(Menu, postCommand), TK_CONFIG_NULL_OK},
383 {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
384 DEF_MENU_RELIEF, Tk_Offset(Menu, relief), 0},
385 {TK_CONFIG_COLOR, "-selectcolor", "selectColor", "Background",
386 DEF_MENU_SELECT_COLOR, Tk_Offset(Menu, indicatorFg),
387 TK_CONFIG_COLOR_ONLY},
388 {TK_CONFIG_COLOR, "-selectcolor", "selectColor", "Background",
389 DEF_MENU_SELECT_MONO, Tk_Offset(Menu, indicatorFg),
390 TK_CONFIG_MONO_ONLY},
391 {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
392 DEF_MENU_TAKE_FOCUS, Tk_Offset(Menu, takeFocus), TK_CONFIG_NULL_OK},
393 {TK_CONFIG_BOOLEAN, "-tearoff", "tearOff", "TearOff",
394 DEF_MENU_TEAROFF, Tk_Offset(Menu, tearOff), 0},
395 {TK_CONFIG_STRING, "-tearoffcommand", "tearOffCommand", "TearOffCommand",
396 DEF_MENU_TEAROFF_CMD, Tk_Offset(Menu, tearOffCommand),
397 TK_CONFIG_NULL_OK},
398 {TK_CONFIG_BOOLEAN, "-transient", "transient", "Transient",
399 DEF_MENU_TRANSIENT, Tk_Offset(Menu, transient), 0},
400 {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
401 (char *) NULL, 0, 0}
402 };
403
404 /*
405 * Various geometry definitions:
406 */
407
408 #define CASCADE_ARROW_HEIGHT 10
409 #define CASCADE_ARROW_WIDTH 8
410 #define DECORATION_BORDER_WIDTH 2
411 #define MARGIN_WIDTH 2
412
413 /*
414 * Forward declarations for procedures defined later in this file:
415 */
416
417 static int ActivateMenuEntry _ANSI_ARGS_((Menu *menuPtr,
418 int index));
419 static void ComputeMenuGeometry _ANSI_ARGS_((
420 ClientData clientData));
421 static int ConfigureMenu _ANSI_ARGS_((Tcl_Interp *interp,
422 Menu *menuPtr, int argc, char **argv,
423 int flags));
424 static int ConfigureMenuEntry _ANSI_ARGS_((Tcl_Interp *interp,
425 Menu *menuPtr, MenuEntry *mePtr, int index,
426 int argc, char **argv, int flags));
427 static void DestroyMenu _ANSI_ARGS_((char *memPtr));
428 static void DestroyMenuEntry _ANSI_ARGS_((char *memPtr));
429 static void DisplayMenu _ANSI_ARGS_((ClientData clientData));
430 static void EventuallyRedrawMenu _ANSI_ARGS_((Menu *menuPtr,
431 MenuEntry *mePtr));
432 static int GetMenuIndex _ANSI_ARGS_((Tcl_Interp *interp,
433 Menu *menuPtr, char *string, int lastOK,
434 int *indexPtr));
435 static int MenuAddOrInsert _ANSI_ARGS_((Tcl_Interp *interp,
436 Menu *menuPtr, char *indexString, int argc,
437 char **argv));
438 static void MenuCmdDeletedProc _ANSI_ARGS_((
439 ClientData clientData));
440 static void MenuEventProc _ANSI_ARGS_((ClientData clientData,
441 XEvent *eventPtr));
442 static void MenuImageProc _ANSI_ARGS_((ClientData clientData,
443 int x, int y, int width, int height, int imgWidth,
444 int imgHeight));
445 static MenuEntry * MenuNewEntry _ANSI_ARGS_((Menu *menuPtr, int index,
446 int type));
447 static void MenuSelectImageProc _ANSI_ARGS_((ClientData clientData,
448 int x, int y, int width, int height, int imgWidth,
449 int imgHeight));
450 static char * MenuVarProc _ANSI_ARGS_((ClientData clientData,
451 Tcl_Interp *interp, char *name1, char *name2,
452 int flags));
453 static int MenuWidgetCmd _ANSI_ARGS_((ClientData clientData,
454 Tcl_Interp *interp, int argc, char **argv));
455 static int PostSubmenu _ANSI_ARGS_((Tcl_Interp *interp,
456 Menu *menuPtr, MenuEntry *mePtr));
457
458 /*
459 *--------------------------------------------------------------
460 *
461 * Tk_MenuCmd --
462 *
463 * This procedure is invoked to process the "menu" Tcl
464 * command. See the user documentation for details on
465 * what it does.
466 *
467 * Results:
468 * A standard Tcl result.
469 *
470 * Side effects:
471 * See the user documentation.
472 *
473 *--------------------------------------------------------------
474 */
475
476 int
Tk_MenuCmd(clientData,interp,argc,argv)477 Tk_MenuCmd(clientData, interp, argc, argv)
478 ClientData clientData; /* Main window associated with
479 * interpreter. */
480 Tcl_Interp *interp; /* Current interpreter. */
481 int argc; /* Number of arguments. */
482 char **argv; /* Argument strings. */
483 {
484 Tk_Window tkwin = (Tk_Window) clientData;
485 Tk_Window new;
486 register Menu *menuPtr;
487
488 if (argc < 2) {
489 Tcl_AppendResult(interp, "wrong # args: should be \"",
490 argv[0], " pathName ?options?\"", (char *) NULL);
491 return TCL_ERROR;
492 }
493
494 /*
495 * Create the new window. Set override-redirect so the window
496 * manager won't add a border or argue about placement, and set
497 * save-under so that the window can pop up and down without a
498 * lot of re-drawing.
499 */
500
501 new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], "");
502 if (new == NULL) {
503 return TCL_ERROR;
504 }
505
506 /*
507 * Initialize the data structure for the menu.
508 */
509
510 menuPtr = (Menu *) ckalloc(sizeof(Menu));
511 menuPtr->tkwin = new;
512 menuPtr->display = Tk_Display(new);
513 menuPtr->interp = interp;
514 menuPtr->widgetCmd = Tcl_CreateCommand(interp,
515 Tk_PathName(menuPtr->tkwin), MenuWidgetCmd,
516 (ClientData) menuPtr, MenuCmdDeletedProc);
517 menuPtr->entries = NULL;
518 menuPtr->numEntries = 0;
519 menuPtr->active = -1;
520 menuPtr->border = NULL;
521 menuPtr->borderWidth = 0;
522 menuPtr->relief = TK_RELIEF_FLAT;
523 menuPtr->activeBorder = NULL;
524 menuPtr->activeBorderWidth = 0;
525 menuPtr->fontPtr = NULL;
526 menuPtr->fg = NULL;
527 menuPtr->textGC = None;
528 menuPtr->disabledFg = NULL;
529 menuPtr->gray = None;
530 menuPtr->disabledGC = None;
531 menuPtr->activeFg = NULL;
532 menuPtr->activeGC = None;
533 menuPtr->indicatorFg = NULL;
534 menuPtr->indicatorGC = None;
535 menuPtr->indicatorSpace = 0;
536 menuPtr->labelWidth = 0;
537 menuPtr->tearOff = 1;
538 menuPtr->tearOffCommand = NULL;
539 menuPtr->cursor = None;
540 menuPtr->takeFocus = NULL;
541 menuPtr->postCommand = NULL;
542 menuPtr->postedCascade = NULL;
543 menuPtr->flags = 0;
544
545 Tk_SetClass(new, "Menu");
546 Tk_CreateEventHandler(menuPtr->tkwin, ExposureMask|StructureNotifyMask,
547 MenuEventProc, (ClientData) menuPtr);
548 if (ConfigureMenu(interp, menuPtr, argc-2, argv+2, 0) != TCL_OK) {
549 goto error;
550 }
551
552 interp->result = Tk_PathName(menuPtr->tkwin);
553 return TCL_OK;
554
555 error:
556 Tk_DestroyWindow(menuPtr->tkwin);
557 return TCL_ERROR;
558 }
559
560 /*
561 *--------------------------------------------------------------
562 *
563 * MenuWidgetCmd --
564 *
565 * This procedure is invoked to process the Tcl command
566 * that corresponds to a widget managed by this module.
567 * See the user documentation for details on what it does.
568 *
569 * Results:
570 * A standard Tcl result.
571 *
572 * Side effects:
573 * See the user documentation.
574 *
575 *--------------------------------------------------------------
576 */
577
578 static int
MenuWidgetCmd(clientData,interp,argc,argv)579 MenuWidgetCmd(clientData, interp, argc, argv)
580 ClientData clientData; /* Information about menu widget. */
581 Tcl_Interp *interp; /* Current interpreter. */
582 int argc; /* Number of arguments. */
583 char **argv; /* Argument strings. */
584 {
585 register Menu *menuPtr = (Menu *) clientData;
586 register MenuEntry *mePtr;
587 int result = TCL_OK;
588 size_t length;
589 int c;
590
591 if (argc < 2) {
592 Tcl_AppendResult(interp, "wrong # args: should be \"",
593 argv[0], " option ?arg arg ...?\"", (char *) NULL);
594 return TCL_ERROR;
595 }
596 Tcl_Preserve((ClientData) menuPtr);
597 c = argv[1][0];
598 length = strlen(argv[1]);
599 if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)
600 && (length >= 2)) {
601 int index;
602
603 if (argc != 3) {
604 Tcl_AppendResult(interp, "wrong # args: should be \"",
605 argv[0], " activate index\"", (char *) NULL);
606 goto error;
607 }
608 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
609 goto error;
610 }
611 if (menuPtr->active == index) {
612 goto done;
613 }
614 if (index >= 0) {
615 if ((menuPtr->entries[index]->type == SEPARATOR_ENTRY)
616 || (menuPtr->entries[index]->state == tkDisabledUid)) {
617 index = -1;
618 }
619 }
620 result = ActivateMenuEntry(menuPtr, index);
621 } else if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)
622 && (length >= 2)) {
623 if (argc < 3) {
624 Tcl_AppendResult(interp, "wrong # args: should be \"",
625 argv[0], " add type ?options?\"", (char *) NULL);
626 goto error;
627 }
628 if (MenuAddOrInsert(interp, menuPtr, (char *) NULL,
629 argc-2, argv+2) != TCL_OK) {
630 goto error;
631 }
632 } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
633 && (length >= 2)) {
634 if (argc != 3) {
635 Tcl_AppendResult(interp, "wrong # args: should be \"",
636 argv[0], " cget option\"",
637 (char *) NULL);
638 goto error;
639 }
640 result = Tk_ConfigureValue(interp, menuPtr->tkwin, configSpecs,
641 (char *) menuPtr, argv[2], 0);
642 } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
643 && (length >= 2)) {
644 if (argc == 2) {
645 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
646 (char *) menuPtr, (char *) NULL, 0);
647 } else if (argc == 3) {
648 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
649 (char *) menuPtr, argv[2], 0);
650 } else {
651 result = ConfigureMenu(interp, menuPtr, argc-2, argv+2,
652 TK_CONFIG_ARGV_ONLY);
653 }
654 } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)) {
655 int first, last, i, numDeleted;
656
657 if ((argc != 3) && (argc != 4)) {
658 Tcl_AppendResult(interp, "wrong # args: should be \"",
659 argv[0], " delete first ?last?\"", (char *) NULL);
660 goto error;
661 }
662 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &first) != TCL_OK) {
663 goto error;
664 }
665 if (argc == 3) {
666 last = first;
667 } else {
668 if (GetMenuIndex(interp, menuPtr, argv[3], 0, &last) != TCL_OK) {
669 goto error;
670 }
671 }
672 if (menuPtr->tearOff && (first == 0)) {
673 /*
674 * Sorry, can't delete the tearoff entry; must reconfigure
675 * the menu.
676 */
677 first = 1;
678 }
679 if ((first < 0) || (last < first)) {
680 goto done;
681 }
682 numDeleted = last + 1 - first;
683 for (i = first; i <= last; i++) {
684 Tcl_EventuallyFree((ClientData) menuPtr->entries[i],
685 DestroyMenuEntry);
686 }
687 for (i = last+1; i < menuPtr->numEntries; i++) {
688 menuPtr->entries[i-numDeleted] = menuPtr->entries[i];
689 }
690 menuPtr->numEntries -= numDeleted;
691 if ((menuPtr->active >= first) && (menuPtr->active <= last)) {
692 menuPtr->active = -1;
693 } else if (menuPtr->active > last) {
694 menuPtr->active -= numDeleted;
695 }
696 if (!(menuPtr->flags & RESIZE_PENDING)) {
697 menuPtr->flags |= RESIZE_PENDING;
698 Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
699 }
700 } else if ((c == 'e') && (length >= 7)
701 && (strncmp(argv[1], "entrycget", length) == 0)) {
702 int index;
703
704 if (argc != 4) {
705 Tcl_AppendResult(interp, "wrong # args: should be \"",
706 argv[0], " entrycget index option\"",
707 (char *) NULL);
708 goto error;
709 }
710 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
711 goto error;
712 }
713 if (index < 0) {
714 goto done;
715 }
716 mePtr = menuPtr->entries[index];
717 Tcl_Preserve((ClientData) mePtr);
718 result = Tk_ConfigureValue(interp, menuPtr->tkwin, entryConfigSpecs,
719 (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
720 Tcl_Release((ClientData) mePtr);
721 } else if ((c == 'e') && (length >= 7)
722 && (strncmp(argv[1], "entryconfigure", length) == 0)) {
723 int index;
724
725 if (argc < 3) {
726 Tcl_AppendResult(interp, "wrong # args: should be \"",
727 argv[0], " entryconfigure index ?option value ...?\"",
728 (char *) NULL);
729 goto error;
730 }
731 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
732 goto error;
733 }
734 if (index < 0) {
735 goto done;
736 }
737 mePtr = menuPtr->entries[index];
738 Tcl_Preserve((ClientData) mePtr);
739 if (argc == 3) {
740 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
741 (char *) mePtr, (char *) NULL,
742 COMMAND_MASK << mePtr->type);
743 } else if (argc == 4) {
744 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
745 (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
746 } else {
747 result = ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc-3,
748 argv+3, TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
749 }
750 Tcl_Release((ClientData) mePtr);
751 } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
752 && (length >= 3)) {
753 int index;
754
755 if (argc != 3) {
756 Tcl_AppendResult(interp, "wrong # args: should be \"",
757 argv[0], " index string\"", (char *) NULL);
758 goto error;
759 }
760 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
761 goto error;
762 }
763 if (index < 0) {
764 interp->result = "none";
765 } else {
766 sprintf(interp->result, "%d", index);
767 }
768 } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
769 && (length >= 3)) {
770 if (argc < 4) {
771 Tcl_AppendResult(interp, "wrong # args: should be \"",
772 argv[0], " insert index type ?options?\"", (char *) NULL);
773 goto error;
774 }
775 if (MenuAddOrInsert(interp, menuPtr, argv[2],
776 argc-3, argv+3) != TCL_OK) {
777 goto error;
778 }
779 } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0)
780 && (length >= 3)) {
781 int index;
782
783 if (argc != 3) {
784 Tcl_AppendResult(interp, "wrong # args: should be \"",
785 argv[0], " invoke index\"", (char *) NULL);
786 goto error;
787 }
788 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
789 goto error;
790 }
791 if (index < 0) {
792 goto done;
793 }
794 mePtr = menuPtr->entries[index];
795 if (mePtr->state == tkDisabledUid) {
796 goto done;
797 }
798 Tcl_Preserve((ClientData) mePtr);
799 if (mePtr->type == CHECK_BUTTON_ENTRY) {
800 if (mePtr->flags & ENTRY_SELECTED) {
801 if (Tcl_SetVar(interp, mePtr->name, mePtr->offValue,
802 TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
803 result = TCL_ERROR;
804 }
805 } else {
806 if (Tcl_SetVar(interp, mePtr->name, mePtr->onValue,
807 TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
808 result = TCL_ERROR;
809 }
810 }
811 } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
812 if (Tcl_SetVar(interp, mePtr->name, mePtr->onValue,
813 TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
814 result = TCL_ERROR;
815 }
816 }
817 if ((result == TCL_OK) && (mePtr->command != NULL)) {
818 result = TkCopyAndGlobalEval(interp, mePtr->command);
819 }
820 if ((result == TCL_OK) && (mePtr->type == CASCADE_ENTRY)) {
821 result = PostSubmenu(menuPtr->interp, menuPtr, mePtr);
822 }
823 Tcl_Release((ClientData) mePtr);
824 } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0)
825 && (length == 4)) {
826 int x, y, tmp, vRootX, vRootY;
827 int vRootWidth, vRootHeight;
828
829 if (argc != 4) {
830 Tcl_AppendResult(interp, "wrong # args: should be \"",
831 argv[0], " post x y\"", (char *) NULL);
832 goto error;
833 }
834 if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK)
835 || (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) {
836 goto error;
837 }
838
839 /*
840 * De-activate any active element.
841 */
842
843 ActivateMenuEntry(menuPtr, -1);
844
845 /*
846 * If there is a command for the menu, execute it. This
847 * may change the size of the menu, so be sure to recompute
848 * the menu's geometry if needed.
849 */
850
851 if (menuPtr->postCommand != NULL) {
852 result = TkCopyAndGlobalEval(menuPtr->interp,
853 menuPtr->postCommand);
854 if (result != TCL_OK) {
855 return result;
856 }
857 if (menuPtr->flags & RESIZE_PENDING) {
858 Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
859 ComputeMenuGeometry((ClientData) menuPtr);
860 }
861 }
862
863 /*
864 * Adjust the position of the menu if necessary to keep it
865 * visible on the screen. There are two special tricks to
866 * make this work right:
867 *
868 * 1. If a virtual root window manager is being used then
869 * the coordinates are in the virtual root window of
870 * menuPtr's parent; since the menu uses override-redirect
871 * mode it will be in the *real* root window for the screen,
872 * so we have to map the coordinates from the virtual root
873 * (if any) to the real root. Can't get the virtual root
874 * from the menu itself (it will never be seen by the wm)
875 * so use its parent instead (it would be better to have an
876 * an option that names a window to use for this...).
877 * 2. The menu may not have been mapped yet, so its current size
878 * might be the default 1x1. To compute how much space it
879 * needs, use its requested size, not its actual size.
880 */
881
882 Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY,
883 &vRootWidth, &vRootHeight);
884 x += vRootX;
885 y += vRootY;
886 tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin))
887 - Tk_ReqWidth(menuPtr->tkwin);
888 if (x > tmp) {
889 x = tmp;
890 }
891 if (x < 0) {
892 x = 0;
893 }
894 tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin))
895 - Tk_ReqHeight(menuPtr->tkwin);
896 if (y > tmp) {
897 y = tmp;
898 }
899 if (y < 0) {
900 y = 0;
901 }
902 if ((x != Tk_X(menuPtr->tkwin)) || (y != Tk_Y(menuPtr->tkwin))) {
903 Tk_MoveToplevelWindow(menuPtr->tkwin, x, y);
904 }
905 if (!Tk_IsMapped(menuPtr->tkwin)) {
906 Tk_MapWindow(menuPtr->tkwin);
907 }
908 XRaiseWindow(menuPtr->display, Tk_WindowId(menuPtr->tkwin));
909 } else if ((c == 'p') && (strncmp(argv[1], "postcascade", length) == 0)
910 && (length > 4)) {
911 int index;
912 if (argc != 3) {
913 Tcl_AppendResult(interp, "wrong # args: should be \"",
914 argv[0], " postcascade index\"", (char *) NULL);
915 goto error;
916 }
917 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
918 goto error;
919 }
920 if ((index < 0) || (menuPtr->entries[index]->type != CASCADE_ENTRY)) {
921 result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
922 } else {
923 result = PostSubmenu(interp, menuPtr, menuPtr->entries[index]);
924 }
925 } else if ((c == 't') && (strncmp(argv[1], "type", length) == 0)) {
926 int index;
927 if (argc != 3) {
928 Tcl_AppendResult(interp, "wrong # args: should be \"",
929 argv[0], " type index\"", (char *) NULL);
930 goto error;
931 }
932 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
933 goto error;
934 }
935 if (index < 0) {
936 goto done;
937 }
938 mePtr = menuPtr->entries[index];
939 switch (mePtr->type) {
940 case COMMAND_ENTRY:
941 interp->result = "command";
942 break;
943 case SEPARATOR_ENTRY:
944 interp->result = "separator";
945 break;
946 case CHECK_BUTTON_ENTRY:
947 interp->result = "checkbutton";
948 break;
949 case RADIO_BUTTON_ENTRY:
950 interp->result = "radiobutton";
951 break;
952 case CASCADE_ENTRY:
953 interp->result = "cascade";
954 break;
955 case TEAROFF_ENTRY:
956 interp->result = "tearoff";
957 break;
958 }
959 } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) {
960 if (argc != 2) {
961 Tcl_AppendResult(interp, "wrong # args: should be \"",
962 argv[0], " unpost\"", (char *) NULL);
963 goto error;
964 }
965 Tk_UnmapWindow(menuPtr->tkwin);
966 if (result == TCL_OK) {
967 result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
968 }
969 } else if ((c == 'y') && (strncmp(argv[1], "yposition", length) == 0)) {
970 int index;
971
972 if (argc != 3) {
973 Tcl_AppendResult(interp, "wrong # args: should be \"",
974 argv[0], " yposition index\"", (char *) NULL);
975 goto error;
976 }
977 if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
978 goto error;
979 }
980 if (index < 0) {
981 interp->result = "0";
982 } else {
983 sprintf(interp->result, "%d", menuPtr->entries[index]->y);
984 }
985 } else {
986 Tcl_AppendResult(interp, "bad option \"", argv[1],
987 "\": must be activate, add, cget, configure, delete, ",
988 "entrycget, entryconfigure, index, insert, invoke, ",
989 "post, postcascade, type, unpost, or yposition",
990 (char *) NULL);
991 goto error;
992 }
993 done:
994 Tcl_Release((ClientData) menuPtr);
995 return result;
996
997 error:
998 Tcl_Release((ClientData) menuPtr);
999 return TCL_ERROR;
1000 }
1001
1002 /*
1003 *----------------------------------------------------------------------
1004 *
1005 * DestroyMenu --
1006 *
1007 * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
1008 * to clean up the internal structure of a menu at a safe time
1009 * (when no-one is using it anymore).
1010 *
1011 * Results:
1012 * None.
1013 *
1014 * Side effects:
1015 * Everything associated with the menu is freed up.
1016 *
1017 *----------------------------------------------------------------------
1018 */
1019
1020 static void
DestroyMenu(memPtr)1021 DestroyMenu(memPtr)
1022 char *memPtr; /* Info about menu widget. */
1023 {
1024 register Menu *menuPtr = (Menu *) memPtr;
1025 int i;
1026
1027 /*
1028 * Free up all the stuff that requires special handling, then
1029 * let Tk_FreeOptions handle all the standard option-related
1030 * stuff.
1031 */
1032
1033 for (i = 0; i < menuPtr->numEntries; i++) {
1034 DestroyMenuEntry((char *) menuPtr->entries[i]);
1035 }
1036 if (menuPtr->entries != NULL) {
1037 ckfree((char *) menuPtr->entries);
1038 }
1039 if (menuPtr->textGC != None) {
1040 Tk_FreeGC(menuPtr->display, menuPtr->textGC);
1041 }
1042 if (menuPtr->gray != None) {
1043 Tk_FreeBitmap(menuPtr->display, menuPtr->gray);
1044 }
1045 if (menuPtr->disabledGC != None) {
1046 Tk_FreeGC(menuPtr->display, menuPtr->disabledGC);
1047 }
1048 if (menuPtr->activeGC != None) {
1049 Tk_FreeGC(menuPtr->display, menuPtr->activeGC);
1050 }
1051 if (menuPtr->indicatorGC != None) {
1052 Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC);
1053 }
1054 Tk_FreeOptions(configSpecs, (char *) menuPtr, menuPtr->display, 0);
1055 ckfree((char *) menuPtr);
1056 }
1057
1058 /*
1059 *----------------------------------------------------------------------
1060 *
1061 * DestroyMenuEntry --
1062 *
1063 * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
1064 * to clean up the internal structure of a menu entry at a safe time
1065 * (when no-one is using it anymore).
1066 *
1067 * Results:
1068 * None.
1069 *
1070 * Side effects:
1071 * Everything associated with the menu entry is freed up.
1072 *
1073 *----------------------------------------------------------------------
1074 */
1075
1076 static void
DestroyMenuEntry(memPtr)1077 DestroyMenuEntry(memPtr)
1078 char *memPtr; /* Pointer to entry to be freed. */
1079 {
1080 register MenuEntry *mePtr = (MenuEntry *) memPtr;
1081 Menu *menuPtr = mePtr->menuPtr;
1082
1083 /*
1084 * Free up all the stuff that requires special handling, then
1085 * let Tk_FreeOptions handle all the standard option-related
1086 * stuff.
1087 */
1088
1089 if (menuPtr->postedCascade == mePtr) {
1090 /*
1091 * Ignore errors while unposting the menu, since it's possible
1092 * that the menu has already been deleted and the unpost will
1093 * generate an error.
1094 */
1095
1096 PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL);
1097 }
1098 if (mePtr->image != NULL) {
1099 Tk_FreeImage(mePtr->image);
1100 }
1101 if (mePtr->selectImage != NULL) {
1102 Tk_FreeImage(mePtr->image);
1103 }
1104 if (mePtr->textGC != None) {
1105 Tk_FreeGC(menuPtr->display, mePtr->textGC);
1106 }
1107 if (mePtr->activeGC != None) {
1108 Tk_FreeGC(menuPtr->display, mePtr->activeGC);
1109 }
1110 if (mePtr->disabledGC != None) {
1111 Tk_FreeGC(menuPtr->display, mePtr->disabledGC);
1112 }
1113 if (mePtr->indicatorGC != None) {
1114 Tk_FreeGC(menuPtr->display, mePtr->indicatorGC);
1115 }
1116 if (mePtr->name != NULL) {
1117 Tcl_UntraceVar(menuPtr->interp, mePtr->name,
1118 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1119 MenuVarProc, (ClientData) mePtr);
1120 }
1121 Tk_FreeOptions(entryConfigSpecs, (char *) mePtr, menuPtr->display,
1122 (COMMAND_MASK << mePtr->type));
1123 ckfree((char *) mePtr);
1124 }
1125
1126 /*
1127 *----------------------------------------------------------------------
1128 *
1129 * ConfigureMenu --
1130 *
1131 * This procedure is called to process an argv/argc list, plus
1132 * the Tk option database, in order to configure (or
1133 * reconfigure) a menu widget.
1134 *
1135 * Results:
1136 * The return value is a standard Tcl result. If TCL_ERROR is
1137 * returned, then interp->result contains an error message.
1138 *
1139 * Side effects:
1140 * Configuration information, such as colors, font, etc. get set
1141 * for menuPtr; old resources get freed, if there were any.
1142 *
1143 *----------------------------------------------------------------------
1144 */
1145
1146 static int
ConfigureMenu(interp,menuPtr,argc,argv,flags)1147 ConfigureMenu(interp, menuPtr, argc, argv, flags)
1148 Tcl_Interp *interp; /* Used for error reporting. */
1149 register Menu *menuPtr; /* Information about widget; may or may
1150 * not already have values for some fields. */
1151 int argc; /* Number of valid entries in argv. */
1152 char **argv; /* Arguments. */
1153 int flags; /* Flags to pass to Tk_ConfigureWidget. */
1154 {
1155 XSetWindowAttributes atts;
1156 XGCValues gcValues;
1157 GC newGC;
1158 unsigned long mask;
1159 int i;
1160
1161 if (Tk_ConfigureWidget(interp, menuPtr->tkwin, configSpecs,
1162 argc, argv, (char *) menuPtr, flags) != TCL_OK) {
1163 return TCL_ERROR;
1164 }
1165
1166 /*
1167 * A few options need special processing, such as setting the
1168 * background from a 3-D border, or filling in complicated
1169 * defaults that couldn't be specified to Tk_ConfigureWidget.
1170 */
1171
1172 Tk_SetBackgroundFromBorder(menuPtr->tkwin, menuPtr->border);
1173
1174 gcValues.font = menuPtr->fontPtr->fid;
1175 gcValues.foreground = menuPtr->fg->pixel;
1176 gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel;
1177 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
1178 &gcValues);
1179 if (menuPtr->textGC != None) {
1180 Tk_FreeGC(menuPtr->display, menuPtr->textGC);
1181 }
1182 menuPtr->textGC = newGC;
1183
1184 if (menuPtr->disabledFg != NULL) {
1185 gcValues.foreground = menuPtr->disabledFg->pixel;
1186 mask = GCForeground|GCBackground|GCFont;
1187 } else {
1188 gcValues.foreground = gcValues.background;
1189 if (menuPtr->gray == None) {
1190 menuPtr->gray = Tk_GetBitmap(interp, menuPtr->tkwin,
1191 Tk_GetUid("gray50"));
1192 if (menuPtr->gray == None) {
1193 return TCL_ERROR;
1194 }
1195 }
1196 gcValues.fill_style = FillStippled;
1197 gcValues.stipple = menuPtr->gray;
1198 mask = GCForeground|GCFillStyle|GCStipple;
1199 }
1200 newGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
1201 if (menuPtr->disabledGC != None) {
1202 Tk_FreeGC(menuPtr->display, menuPtr->disabledGC);
1203 }
1204 menuPtr->disabledGC = newGC;
1205
1206 gcValues.font = menuPtr->fontPtr->fid;
1207 gcValues.foreground = menuPtr->activeFg->pixel;
1208 gcValues.background = Tk_3DBorderColor(menuPtr->activeBorder)->pixel;
1209 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
1210 &gcValues);
1211 if (menuPtr->activeGC != None) {
1212 Tk_FreeGC(menuPtr->display, menuPtr->activeGC);
1213 }
1214 menuPtr->activeGC = newGC;
1215
1216 gcValues.foreground = menuPtr->indicatorFg->pixel;
1217 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCFont, &gcValues);
1218 if (menuPtr->indicatorGC != None) {
1219 Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC);
1220 }
1221 menuPtr->indicatorGC = newGC;
1222
1223 if (menuPtr->transient) {
1224 atts.override_redirect = True;
1225 atts.save_under = True;
1226 } else {
1227 atts.override_redirect = False;
1228 atts.save_under = False;
1229 }
1230 if ((atts.override_redirect
1231 != Tk_Attributes(menuPtr->tkwin)->override_redirect)
1232 || (atts.save_under
1233 != Tk_Attributes(menuPtr->tkwin)->save_under)) {
1234 Tk_ChangeWindowAttributes(menuPtr->tkwin,
1235 CWOverrideRedirect|CWSaveUnder, &atts);
1236 }
1237 #ifdef MAC_TCL
1238 TkMacMakeMenuWindow(menuPtr->tkwin, menuPtr->transient);
1239 #endif
1240
1241 /*
1242 * After reconfiguring a menu, we need to reconfigure all of the
1243 * entries in the menu, since some of the things in the children
1244 * (such as graphics contexts) may have to change to reflect changes
1245 * in the parent.
1246 */
1247
1248 for (i = 0; i < menuPtr->numEntries; i++) {
1249 MenuEntry *mePtr;
1250
1251 mePtr = menuPtr->entries[i];
1252 ConfigureMenuEntry(interp, menuPtr, mePtr, i, 0, (char **) NULL,
1253 TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
1254 }
1255
1256 /*
1257 * Depending on the -tearOff option, make sure that there is or
1258 * isn't an initial tear-off entry at the beginning of the menu.
1259 */
1260
1261 if (menuPtr->tearOff) {
1262 if ((menuPtr->numEntries == 0)
1263 || (menuPtr->entries[0]->type != TEAROFF_ENTRY)) {
1264 MenuNewEntry(menuPtr, 0, TEAROFF_ENTRY);
1265 }
1266 } else if ((menuPtr->numEntries > 0)
1267 && (menuPtr->entries[0]->type == TEAROFF_ENTRY)) {
1268 Tcl_EventuallyFree((ClientData) menuPtr->entries[0],
1269 DestroyMenuEntry);
1270 for (i = 1; i < menuPtr->numEntries; i++) {
1271 menuPtr->entries[i-1] = menuPtr->entries[i];
1272 }
1273 menuPtr->numEntries--;
1274 }
1275
1276 if (!(menuPtr->flags & RESIZE_PENDING)) {
1277 menuPtr->flags |= RESIZE_PENDING;
1278 Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
1279 }
1280
1281 return TCL_OK;
1282 }
1283
1284 /*
1285 *----------------------------------------------------------------------
1286 *
1287 * ConfigureMenuEntry --
1288 *
1289 * This procedure is called to process an argv/argc list, plus
1290 * the Tk option database, in order to configure (or
1291 * reconfigure) one entry in a menu.
1292 *
1293 * Results:
1294 * The return value is a standard Tcl result. If TCL_ERROR is
1295 * returned, then interp->result contains an error message.
1296 *
1297 * Side effects:
1298 * Configuration information such as label and accelerator get
1299 * set for mePtr; old resources get freed, if there were any.
1300 *
1301 *----------------------------------------------------------------------
1302 */
1303
1304 static int
ConfigureMenuEntry(interp,menuPtr,mePtr,index,argc,argv,flags)1305 ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc, argv, flags)
1306 Tcl_Interp *interp; /* Used for error reporting. */
1307 Menu *menuPtr; /* Information about whole menu. */
1308 register MenuEntry *mePtr; /* Information about menu entry; may
1309 * or may not already have values for
1310 * some fields. */
1311 int index; /* Index of mePtr within menuPtr's
1312 * entries. */
1313 int argc; /* Number of valid entries in argv. */
1314 char **argv; /* Arguments. */
1315 int flags; /* Additional flags to pass to
1316 * Tk_ConfigureWidget. */
1317 {
1318 XGCValues gcValues;
1319 GC newGC, newActiveGC, newDisabledGC;
1320 unsigned long mask;
1321 Tk_Image image;
1322
1323 /*
1324 * If this entry is a cascade and the cascade is posted, then unpost
1325 * it before reconfiguring the entry (otherwise the reconfigure might
1326 * change the name of the cascaded entry, leaving a posted menu
1327 * high and dry).
1328 */
1329
1330 if (menuPtr->postedCascade == mePtr) {
1331 if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL)
1332 != TCL_OK) {
1333 Tcl_BackgroundError(menuPtr->interp);
1334 }
1335 }
1336
1337 /*
1338 * If this entry is a check button or radio button, then remove
1339 * its old trace procedure.
1340 */
1341
1342 if ((mePtr->name != NULL) &&
1343 ((mePtr->type == CHECK_BUTTON_ENTRY)
1344 || (mePtr->type == RADIO_BUTTON_ENTRY))) {
1345 Tcl_UntraceVar(menuPtr->interp, mePtr->name,
1346 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1347 MenuVarProc, (ClientData) mePtr);
1348 }
1349
1350 if (Tk_ConfigureWidget(interp, menuPtr->tkwin, entryConfigSpecs,
1351 argc, argv, (char *) mePtr,
1352 flags | (COMMAND_MASK << mePtr->type)) != TCL_OK) {
1353 return TCL_ERROR;
1354 }
1355
1356 /*
1357 * The code below handles special configuration stuff not taken
1358 * care of by Tk_ConfigureWidget, such as special processing for
1359 * defaults, sizing strings, graphics contexts, etc.
1360 */
1361
1362 if (mePtr->label == NULL) {
1363 mePtr->labelLength = 0;
1364 } else {
1365 mePtr->labelLength = strlen(mePtr->label);
1366 }
1367 if (mePtr->accel == NULL) {
1368 mePtr->accelLength = 0;
1369 } else {
1370 mePtr->accelLength = strlen(mePtr->accel);
1371 }
1372
1373 if (mePtr->state == tkActiveUid) {
1374 if (index != menuPtr->active) {
1375 ActivateMenuEntry(menuPtr, index);
1376 }
1377 } else {
1378 if (index == menuPtr->active) {
1379 ActivateMenuEntry(menuPtr, -1);
1380 }
1381 if ((mePtr->state != tkNormalUid) && (mePtr->state != tkDisabledUid)) {
1382 Tcl_AppendResult(interp, "bad state value \"", mePtr->state,
1383 "\": must be normal, active, or disabled", (char *) NULL);
1384 mePtr->state = tkNormalUid;
1385 return TCL_ERROR;
1386 }
1387 }
1388
1389 if ((mePtr->fontPtr != NULL) || (mePtr->border != NULL)
1390 || (mePtr->fg != NULL) || (mePtr->activeBorder != NULL)
1391 || (mePtr->activeFg != NULL)) {
1392 gcValues.foreground = (mePtr->fg != NULL) ? mePtr->fg->pixel
1393 : menuPtr->fg->pixel;
1394 gcValues.background = Tk_3DBorderColor(
1395 (mePtr->border != NULL) ? mePtr->border : menuPtr->border)
1396 ->pixel;
1397 gcValues.font = (mePtr->fontPtr != NULL) ? mePtr->fontPtr->fid
1398 : menuPtr->fontPtr->fid;
1399
1400 /*
1401 * Note: disable GraphicsExpose events; we know there won't be
1402 * obscured areas when copying from an off-screen pixmap to the
1403 * screen and this gets rid of unnecessary events.
1404 */
1405
1406 gcValues.graphics_exposures = False;
1407 newGC = Tk_GetGC(menuPtr->tkwin,
1408 GCForeground|GCBackground|GCFont|GCGraphicsExposures,
1409 &gcValues);
1410
1411 if (menuPtr->disabledFg != NULL) {
1412 gcValues.foreground = menuPtr->disabledFg->pixel;
1413 mask = GCForeground|GCBackground|GCFont|GCGraphicsExposures;
1414 } else {
1415 gcValues.foreground = gcValues.background;
1416 gcValues.fill_style = FillStippled;
1417 gcValues.stipple = menuPtr->gray;
1418 mask = GCForeground|GCFillStyle|GCStipple;
1419 }
1420 newDisabledGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
1421
1422 gcValues.foreground = (mePtr->activeFg != NULL)
1423 ? mePtr->activeFg->pixel : menuPtr->activeFg->pixel;
1424 gcValues.background = Tk_3DBorderColor(
1425 (mePtr->activeBorder != NULL) ? mePtr->activeBorder
1426 : menuPtr->activeBorder)->pixel;
1427 newActiveGC = Tk_GetGC(menuPtr->tkwin,
1428 GCForeground|GCBackground|GCFont|GCGraphicsExposures,
1429 &gcValues);
1430 } else {
1431 newGC = None;
1432 newActiveGC = None;
1433 newDisabledGC = None;
1434 }
1435 if (mePtr->textGC != None) {
1436 Tk_FreeGC(menuPtr->display, mePtr->textGC);
1437 }
1438 mePtr->textGC = newGC;
1439 if (mePtr->activeGC != None) {
1440 Tk_FreeGC(menuPtr->display, mePtr->activeGC);
1441 }
1442 mePtr->activeGC = newActiveGC;
1443 if (mePtr->disabledGC != None) {
1444 Tk_FreeGC(menuPtr->display, mePtr->disabledGC);
1445 }
1446 mePtr->disabledGC = newDisabledGC;
1447 if (mePtr->indicatorFg != NULL) {
1448 gcValues.foreground = mePtr->indicatorFg->pixel;
1449 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground, &gcValues);
1450 } else {
1451 newGC = None;
1452 }
1453 if (mePtr->indicatorGC != None) {
1454 Tk_FreeGC(menuPtr->display, mePtr->indicatorGC);
1455 }
1456 mePtr->indicatorGC = newGC;
1457
1458 if ((mePtr->type == CHECK_BUTTON_ENTRY)
1459 || (mePtr->type == RADIO_BUTTON_ENTRY)) {
1460 char *value;
1461
1462 if (mePtr->name == NULL) {
1463 mePtr->name = (char *) ckalloc((unsigned) (mePtr->labelLength + 1));
1464 strcpy(mePtr->name, (mePtr->label == NULL) ? "" : mePtr->label);
1465 }
1466 if (mePtr->onValue == NULL) {
1467 mePtr->onValue = (char *) ckalloc((unsigned)
1468 (mePtr->labelLength + 1));
1469 strcpy(mePtr->onValue, (mePtr->label == NULL) ? "" : mePtr->label);
1470 }
1471
1472 /*
1473 * Select the entry if the associated variable has the
1474 * appropriate value, initialize the variable if it doesn't
1475 * exist, then set a trace on the variable to monitor future
1476 * changes to its value.
1477 */
1478
1479 value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY);
1480 mePtr->flags &= ~ENTRY_SELECTED;
1481 if (value != NULL) {
1482 if (strcmp(value, mePtr->onValue) == 0) {
1483 mePtr->flags |= ENTRY_SELECTED;
1484 }
1485 } else {
1486 Tcl_SetVar(interp, mePtr->name,
1487 (mePtr->type == CHECK_BUTTON_ENTRY) ? mePtr->offValue : "",
1488 TCL_GLOBAL_ONLY);
1489 }
1490 Tcl_TraceVar(interp, mePtr->name,
1491 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1492 MenuVarProc, (ClientData) mePtr);
1493 }
1494
1495 /*
1496 * Get the images for the entry, if there are any. Allocate the
1497 * new images before freeing the old ones, so that the reference
1498 * counts don't go to zero and cause image data to be discarded.
1499 */
1500
1501 if (mePtr->imageString != NULL) {
1502 image = Tk_GetImage(interp, menuPtr->tkwin, mePtr->imageString,
1503 MenuImageProc, (ClientData) mePtr);
1504 if (image == NULL) {
1505 return TCL_ERROR;
1506 }
1507 } else {
1508 image = NULL;
1509 }
1510 if (mePtr->image != NULL) {
1511 Tk_FreeImage(mePtr->image);
1512 }
1513 mePtr->image = image;
1514 if (mePtr->selectImageString != NULL) {
1515 image = Tk_GetImage(interp, menuPtr->tkwin, mePtr->selectImageString,
1516 MenuSelectImageProc, (ClientData) mePtr);
1517 if (image == NULL) {
1518 return TCL_ERROR;
1519 }
1520 } else {
1521 image = NULL;
1522 }
1523 if (mePtr->selectImage != NULL) {
1524 Tk_FreeImage(mePtr->selectImage);
1525 }
1526 mePtr->selectImage = image;
1527
1528 if (!(menuPtr->flags & RESIZE_PENDING)) {
1529 menuPtr->flags |= RESIZE_PENDING;
1530 Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
1531 }
1532 return TCL_OK;
1533 }
1534
1535 /*
1536 *--------------------------------------------------------------
1537 *
1538 * ComputeMenuGeometry --
1539 *
1540 * This procedure is invoked to recompute the size and
1541 * layout of a menu. It is called as a when-idle handler so
1542 * that it only gets done once, even if a group of changes is
1543 * made to the menu.
1544 *
1545 * Results:
1546 * None.
1547 *
1548 * Side effects:
1549 * Fields of menu entries are changed to reflect their
1550 * current positions, and the size of the menu window
1551 * itself may be changed.
1552 *
1553 *--------------------------------------------------------------
1554 */
1555
1556 static void
ComputeMenuGeometry(clientData)1557 ComputeMenuGeometry(clientData)
1558 ClientData clientData; /* Structure describing menu. */
1559 {
1560 Menu *menuPtr = (Menu *) clientData;
1561 register MenuEntry *mePtr;
1562 XFontStruct *fontPtr;
1563 int maxLabelWidth, maxIndicatorWidth, maxAccelWidth;
1564 int width, height, indicatorSpace;
1565 int i, y;
1566 int imageWidth, imageHeight;
1567
1568 if (menuPtr->tkwin == NULL) {
1569 return;
1570 }
1571
1572 maxLabelWidth = maxIndicatorWidth = maxAccelWidth = 0;
1573 y = menuPtr->borderWidth;
1574
1575 for (i = 0; i < menuPtr->numEntries; i++) {
1576 mePtr = menuPtr->entries[i];
1577 indicatorSpace = 0;
1578 fontPtr = mePtr->fontPtr;
1579 if (fontPtr == NULL) {
1580 fontPtr = menuPtr->fontPtr;
1581 }
1582
1583 /*
1584 * For each entry, compute the height required by that
1585 * particular entry, plus three widths: the width of the
1586 * label, the width to allow for an indicator to be displayed
1587 * to the left of the label (if any), and the width of the
1588 * accelerator to be displayed to the right of the label
1589 * (if any). These sizes depend, of course, on the type
1590 * of the entry.
1591 */
1592
1593 if (mePtr->image != NULL) {
1594 Tk_SizeOfImage(mePtr->image, &imageWidth, &imageHeight);
1595
1596 imageOrBitmap:
1597 mePtr->height = imageHeight;
1598 width = imageWidth;
1599 if (mePtr->indicatorOn) {
1600 if (mePtr->type == CHECK_BUTTON_ENTRY) {
1601 indicatorSpace = (14*mePtr->height)/10;
1602 mePtr->indicatorDiameter = (65*mePtr->height)/100;
1603 } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
1604 indicatorSpace = (14*mePtr->height)/10;
1605 mePtr->indicatorDiameter = (75*mePtr->height)/100;
1606 }
1607 }
1608 } else if (mePtr->bitmap != None) {
1609 Tk_SizeOfBitmap(menuPtr->display, mePtr->bitmap,
1610 &imageWidth, &imageHeight);
1611 goto imageOrBitmap;
1612 } else {
1613 mePtr->height = fontPtr->ascent + fontPtr->descent;
1614 if (mePtr->label != NULL) {
1615 (void) TkMeasureChars(fontPtr, mePtr->label,
1616 mePtr->labelLength, 0, (int) 100000, 0,
1617 TK_NEWLINES_NOT_SPECIAL, &width);
1618 } else {
1619 width = 0;
1620 }
1621 if (mePtr->indicatorOn) {
1622 if (mePtr->type == CHECK_BUTTON_ENTRY) {
1623 indicatorSpace = mePtr->height;
1624 mePtr->indicatorDiameter = (80*mePtr->height)/100;
1625 } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
1626 indicatorSpace = mePtr->height;
1627 mePtr->indicatorDiameter = mePtr->height;
1628 }
1629 }
1630 }
1631 mePtr->height += 2*menuPtr->activeBorderWidth + 2;
1632 if (width > maxLabelWidth) {
1633 maxLabelWidth = width;
1634 }
1635 if (mePtr->type == CASCADE_ENTRY) {
1636 width = 2*CASCADE_ARROW_WIDTH;
1637 } else if (mePtr->accel != NULL) {
1638 (void) TkMeasureChars(fontPtr, mePtr->accel, mePtr->accelLength,
1639 0, (int) 100000, 0, TK_NEWLINES_NOT_SPECIAL, &width);
1640 } else {
1641 width = 0;
1642 }
1643 if (width > maxAccelWidth) {
1644 maxAccelWidth = width;
1645 }
1646 if (mePtr->type == SEPARATOR_ENTRY) {
1647 mePtr->height = 8;
1648 }
1649 if (mePtr->type == TEAROFF_ENTRY) {
1650 mePtr->height = 12;
1651 }
1652 if (indicatorSpace > maxIndicatorWidth) {
1653 maxIndicatorWidth = indicatorSpace;
1654 }
1655 mePtr->y = y;
1656 y += mePtr->height;
1657 }
1658
1659 /*
1660 * Got all the sizes. Update fields in the menu structure, then
1661 * resize the window if necessary. Leave margins on either side
1662 * of the indicator (or just one margin if there is no indicator).
1663 * Leave another margin on the right side of the label, plus yet
1664 * another margin to the right of the accelerator (if there is one).
1665 */
1666
1667 menuPtr->indicatorSpace = maxIndicatorWidth + MARGIN_WIDTH;
1668 if (maxIndicatorWidth != 0) {
1669 menuPtr->indicatorSpace += MARGIN_WIDTH;
1670 }
1671 menuPtr->labelWidth = maxLabelWidth + MARGIN_WIDTH;
1672 width = menuPtr->indicatorSpace + menuPtr->labelWidth + maxAccelWidth
1673 + 2*menuPtr->borderWidth + 2*menuPtr->activeBorderWidth;
1674 if (maxAccelWidth != 0) {
1675 width += MARGIN_WIDTH;
1676 }
1677 height = y + menuPtr->borderWidth;
1678
1679 /*
1680 * The X server doesn't like zero dimensions, so round up to at least
1681 * 1 (a zero-sized menu should never really occur, anyway).
1682 */
1683
1684 if (width <= 0) {
1685 width = 1;
1686 }
1687 if (height <= 0) {
1688 height = 1;
1689 }
1690 if ((width != Tk_ReqWidth(menuPtr->tkwin)) ||
1691 (height != Tk_ReqHeight(menuPtr->tkwin))) {
1692 Tk_GeometryRequest(menuPtr->tkwin, width, height);
1693 } else {
1694 /*
1695 * Must always force a redisplay here if the window is mapped
1696 * (even if the size didn't change, something else might have
1697 * changed in the menu, such as a label or accelerator). The
1698 * resize will force a redisplay above.
1699 */
1700
1701 EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
1702 }
1703
1704 menuPtr->flags &= ~RESIZE_PENDING;
1705 }
1706
1707 /*
1708 *----------------------------------------------------------------------
1709 *
1710 * DisplayMenu --
1711 *
1712 * This procedure is invoked to display a menu widget.
1713 *
1714 * Results:
1715 * None.
1716 *
1717 * Side effects:
1718 * Commands are output to X to display the menu in its
1719 * current mode.
1720 *
1721 *----------------------------------------------------------------------
1722 */
1723
1724 static void
DisplayMenu(clientData)1725 DisplayMenu(clientData)
1726 ClientData clientData; /* Information about widget. */
1727 {
1728 register Menu *menuPtr = (Menu *) clientData;
1729 register MenuEntry *mePtr;
1730 register Tk_Window tkwin = menuPtr->tkwin;
1731 Tk_3DBorder bgBorder, activeBorder;
1732 XFontStruct *fontPtr;
1733 int index, baseline, strictMotif, leftEdge, y, height;
1734 GC gc;
1735 XPoint points[3];
1736
1737 menuPtr->flags &= ~REDRAW_PENDING;
1738 if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
1739 return;
1740 }
1741
1742 /*
1743 * Loop through all of the entries, drawing them one at a time.
1744 */
1745
1746 strictMotif = Tk_StrictMotif(menuPtr->tkwin);
1747 leftEdge = menuPtr->borderWidth + menuPtr->indicatorSpace
1748 + menuPtr->activeBorderWidth;
1749 for (index = 0; index < menuPtr->numEntries; index++) {
1750 mePtr = menuPtr->entries[index];
1751 if (!(mePtr->flags & ENTRY_NEEDS_REDISPLAY)) {
1752 continue;
1753 }
1754 mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY;
1755
1756 /*
1757 * Background.
1758 */
1759
1760 bgBorder = mePtr->border;
1761 if (bgBorder == NULL) {
1762 bgBorder = menuPtr->border;
1763 }
1764 if (strictMotif) {
1765 activeBorder = bgBorder;
1766 } else {
1767 activeBorder = mePtr->activeBorder;
1768 if (activeBorder == NULL) {
1769 activeBorder = menuPtr->activeBorder;
1770 }
1771 }
1772 if (mePtr->state == tkActiveUid) {
1773 bgBorder = activeBorder;
1774 Tk_Fill3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
1775 bgBorder, menuPtr->borderWidth, mePtr->y,
1776 Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
1777 menuPtr->activeBorderWidth, TK_RELIEF_RAISED);
1778 } else {
1779 Tk_Fill3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
1780 bgBorder, menuPtr->borderWidth, mePtr->y,
1781 Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
1782 0, TK_RELIEF_FLAT);
1783 }
1784
1785 /*
1786 * Choose the gc for drawing the foreground part of the entry.
1787 */
1788
1789 if ((mePtr->state == tkActiveUid) && !strictMotif) {
1790 gc = mePtr->activeGC;
1791 if (gc == NULL) {
1792 gc = menuPtr->activeGC;
1793 }
1794 } else {
1795 if ((mePtr->state == tkDisabledUid)
1796 && (menuPtr->disabledFg != NULL)) {
1797 gc = mePtr->disabledGC;
1798 if (gc == NULL) {
1799 gc = menuPtr->disabledGC;
1800 }
1801 } else {
1802 gc = mePtr->textGC;
1803 if (gc == NULL) {
1804 gc = menuPtr->textGC;
1805 }
1806 }
1807 }
1808
1809 /*
1810 * Draw label or bitmap or image for entry.
1811 */
1812
1813 fontPtr = mePtr->fontPtr;
1814 if (fontPtr == NULL) {
1815 fontPtr = menuPtr->fontPtr;
1816 }
1817 baseline = mePtr->y + (mePtr->height + fontPtr->ascent
1818 - fontPtr->descent)/2;
1819 if (mePtr->image != NULL) {
1820 int width, height;
1821
1822 Tk_SizeOfImage(mePtr->image, &width, &height);
1823 if ((mePtr->selectImage != NULL)
1824 && (mePtr->flags & ENTRY_SELECTED)) {
1825 Tk_RedrawImage(mePtr->selectImage, 0, 0, width, height,
1826 Tk_WindowId(tkwin), leftEdge,
1827 (int) (mePtr->y + (mePtr->height - height)/2));
1828 } else {
1829 Tk_RedrawImage(mePtr->image, 0, 0, width, height,
1830 Tk_WindowId(tkwin), leftEdge,
1831 (int) (mePtr->y + (mePtr->height - height)/2));
1832 }
1833 } else if (mePtr->bitmap != None) {
1834 int width, height;
1835
1836 Tk_SizeOfBitmap(menuPtr->display, mePtr->bitmap, &width, &height);
1837 XCopyPlane(menuPtr->display, mePtr->bitmap, Tk_WindowId(tkwin),
1838 gc, 0, 0, (unsigned) width, (unsigned) height, leftEdge,
1839 (int) (mePtr->y + (mePtr->height - height)/2), 1);
1840 } else {
1841 baseline = mePtr->y + (mePtr->height + fontPtr->ascent
1842 - fontPtr->descent)/2;
1843 if (mePtr->label != NULL) {
1844 TkDisplayChars(menuPtr->display, Tk_WindowId(tkwin), gc,
1845 fontPtr, mePtr->label, mePtr->labelLength,
1846 leftEdge, baseline, leftEdge,
1847 TK_NEWLINES_NOT_SPECIAL);
1848 if (mePtr->underline >= 0) {
1849 TkUnderlineChars(menuPtr->display, Tk_WindowId(tkwin), gc,
1850 fontPtr, mePtr->label, leftEdge, baseline,
1851 leftEdge, TK_NEWLINES_NOT_SPECIAL,
1852 mePtr->underline, mePtr->underline);
1853 }
1854 }
1855 }
1856
1857 /*
1858 * Draw accelerator or cascade arrow.
1859 */
1860
1861 if (mePtr->type == CASCADE_ENTRY) {
1862 points[0].x = Tk_Width(tkwin) - menuPtr->borderWidth
1863 - menuPtr->activeBorderWidth - MARGIN_WIDTH
1864 - CASCADE_ARROW_WIDTH;
1865 points[0].y = mePtr->y + (mePtr->height - CASCADE_ARROW_HEIGHT)/2;
1866 points[1].x = points[0].x;
1867 points[1].y = points[0].y + CASCADE_ARROW_HEIGHT;
1868 points[2].x = points[0].x + CASCADE_ARROW_WIDTH;
1869 points[2].y = points[0].y + CASCADE_ARROW_HEIGHT/2;
1870 Tk_Fill3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin), activeBorder,
1871 points, 3, DECORATION_BORDER_WIDTH,
1872 (menuPtr->postedCascade == mePtr) ? TK_RELIEF_SUNKEN
1873 : TK_RELIEF_RAISED);
1874 } else if (mePtr->accel != NULL) {
1875 TkDisplayChars(menuPtr->display, Tk_WindowId(tkwin), gc,
1876 fontPtr, mePtr->accel, mePtr->accelLength,
1877 leftEdge + menuPtr->labelWidth, baseline,
1878 leftEdge + menuPtr->labelWidth, TK_NEWLINES_NOT_SPECIAL);
1879 }
1880
1881 /*
1882 * Draw check-button indicator.
1883 */
1884
1885 gc = mePtr->indicatorGC;
1886 if (gc == None) {
1887 gc = menuPtr->indicatorGC;
1888 }
1889 if ((mePtr->type == CHECK_BUTTON_ENTRY) && mePtr->indicatorOn) {
1890 int dim, x, y;
1891
1892 dim = mePtr->indicatorDiameter;
1893 x = menuPtr->borderWidth + menuPtr->activeBorderWidth
1894 + (menuPtr->indicatorSpace - dim)/2;
1895 y = mePtr->y + (mePtr->height - dim)/2;
1896 Tk_Fill3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
1897 menuPtr->border, x, y, dim, dim,
1898 DECORATION_BORDER_WIDTH, TK_RELIEF_SUNKEN);
1899 x += DECORATION_BORDER_WIDTH;
1900 y += DECORATION_BORDER_WIDTH;
1901 dim -= 2*DECORATION_BORDER_WIDTH;
1902 if ((dim > 0) && (mePtr->flags & ENTRY_SELECTED)) {
1903 XFillRectangle(menuPtr->display, Tk_WindowId(tkwin), gc,
1904 x, y, (unsigned int) dim, (unsigned int) dim);
1905 }
1906 }
1907
1908 /*
1909 * Draw radio-button indicator.
1910 */
1911
1912 if ((mePtr->type == RADIO_BUTTON_ENTRY) && mePtr->indicatorOn) {
1913 XPoint points[4];
1914 int radius;
1915
1916 radius = mePtr->indicatorDiameter/2;
1917 points[0].x = menuPtr->borderWidth + menuPtr->activeBorderWidth
1918 + (menuPtr->indicatorSpace - mePtr->indicatorDiameter)/2;
1919 points[0].y = mePtr->y + (mePtr->height)/2;
1920 points[1].x = points[0].x + radius;
1921 points[1].y = points[0].y + radius;
1922 points[2].x = points[1].x + radius;
1923 points[2].y = points[0].y;
1924 points[3].x = points[1].x;
1925 points[3].y = points[0].y - radius;
1926 if (mePtr->flags & ENTRY_SELECTED) {
1927 XFillPolygon(menuPtr->display, Tk_WindowId(tkwin), gc,
1928 points, 4, Convex, CoordModeOrigin);
1929 } else {
1930 Tk_Fill3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin),
1931 menuPtr->border, points, 4, DECORATION_BORDER_WIDTH,
1932 TK_RELIEF_FLAT);
1933 }
1934 Tk_Draw3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin),
1935 menuPtr->border, points, 4, DECORATION_BORDER_WIDTH,
1936 TK_RELIEF_SUNKEN);
1937 }
1938
1939 /*
1940 * Draw separator.
1941 */
1942
1943 if (mePtr->type == SEPARATOR_ENTRY) {
1944 XPoint points[2];
1945
1946 points[0].x = 0;
1947 points[0].y = mePtr->y + mePtr->height/2;
1948 points[1].x = Tk_Width(tkwin) - 1;
1949 points[1].y = points[0].y;
1950 Tk_Draw3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin),
1951 menuPtr->border, points, 2, 1, TK_RELIEF_RAISED);
1952 }
1953
1954 /*
1955 * Draw tear-off line.
1956 */
1957
1958 if (mePtr->type == TEAROFF_ENTRY) {
1959 XPoint points[2];
1960 int width, maxX;
1961
1962 points[0].x = 0;
1963 points[0].y = mePtr->y + mePtr->height/2;
1964 points[1].y = points[0].y;
1965 width = 6;
1966 maxX = Tk_Width(tkwin) - 1;
1967
1968 while (points[0].x < maxX) {
1969 points[1].x = points[0].x + width;
1970 if (points[1].x > maxX) {
1971 points[1].x = maxX;
1972 }
1973 Tk_Draw3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin),
1974 menuPtr->border, points, 2, 1, TK_RELIEF_RAISED);
1975 points[0].x += 2*width;
1976 }
1977 }
1978
1979 /*
1980 * If the entry is disabled with a stipple rather than a special
1981 * foreground color, generate the stippled effect.
1982 */
1983
1984 if ((mePtr->state == tkDisabledUid) && (menuPtr->disabledFg == NULL)) {
1985 XFillRectangle(menuPtr->display, Tk_WindowId(tkwin),
1986 menuPtr->disabledGC, menuPtr->borderWidth,
1987 mePtr->y,
1988 (unsigned) (Tk_Width(tkwin) - 2*menuPtr->borderWidth),
1989 (unsigned) mePtr->height);
1990 }
1991 }
1992
1993 /*
1994 * If there is extra space after the last entry in the menu,
1995 * clear it.
1996 */
1997
1998 if (menuPtr->numEntries >= 1) {
1999 mePtr = menuPtr->entries[menuPtr->numEntries-1];
2000 y = mePtr->y + mePtr->height;
2001 } else {
2002 y = menuPtr->borderWidth;
2003 }
2004 height = Tk_Height(tkwin) - menuPtr->borderWidth - y;
2005 if (height > 0) {
2006 Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin),
2007 menuPtr->border, menuPtr->borderWidth, y,
2008 Tk_Width(tkwin) - 2*menuPtr->borderWidth,
2009 height, 0, TK_RELIEF_FLAT);
2010 }
2011
2012 Tk_Draw3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
2013 menuPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
2014 menuPtr->borderWidth, menuPtr->relief);
2015 }
2016
2017 /*
2018 *--------------------------------------------------------------
2019 *
2020 * GetMenuIndex --
2021 *
2022 * Parse a textual index into a menu and return the numerical
2023 * index of the indicated entry.
2024 *
2025 * Results:
2026 * A standard Tcl result. If all went well, then *indexPtr is
2027 * filled in with the entry index corresponding to string
2028 * (ranges from -1 to the number of entries in the menu minus
2029 * one). Otherwise an error message is left in interp->result.
2030 *
2031 * Side effects:
2032 * None.
2033 *
2034 *--------------------------------------------------------------
2035 */
2036
2037 static int
GetMenuIndex(interp,menuPtr,string,lastOK,indexPtr)2038 GetMenuIndex(interp, menuPtr, string, lastOK, indexPtr)
2039 Tcl_Interp *interp; /* For error messages. */
2040 Menu *menuPtr; /* Menu for which the index is being
2041 * specified. */
2042 char *string; /* Specification of an entry in menu. See
2043 * manual entry for valid .*/
2044 int lastOK; /* Non-zero means its OK to return index
2045 * just *after* last entry. */
2046 int *indexPtr; /* Where to store converted relief. */
2047 {
2048 int i, y;
2049
2050 if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
2051 *indexPtr = menuPtr->active;
2052 return TCL_OK;
2053 }
2054
2055 if (((string[0] == 'l') && (strcmp(string, "last") == 0))
2056 || ((string[0] == 'e') && (strcmp(string, "end") == 0))) {
2057 *indexPtr = menuPtr->numEntries - ((lastOK) ? 0 : 1);
2058 return TCL_OK;
2059 }
2060
2061 if ((string[0] == 'n') && (strcmp(string, "none") == 0)) {
2062 *indexPtr = -1;
2063 return TCL_OK;
2064 }
2065
2066 if (string[0] == '@') {
2067 if (Tcl_GetInt(interp, string+1, &y) == TCL_OK) {
2068 for (i = 0; i < menuPtr->numEntries; i++) {
2069 MenuEntry *mePtr = menuPtr->entries[i];
2070
2071 if (y < (mePtr->y + mePtr->height)) {
2072 break;
2073 }
2074 }
2075 if (i >= menuPtr->numEntries) {
2076 i = menuPtr->numEntries-1;
2077 }
2078 *indexPtr = i;
2079 return TCL_OK;
2080 } else {
2081 Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
2082 }
2083 }
2084
2085 if (isdigit(UCHAR(string[0]))) {
2086 if (Tcl_GetInt(interp, string, &i) == TCL_OK) {
2087 if (i >= menuPtr->numEntries) {
2088 if (lastOK) {
2089 i = menuPtr->numEntries;
2090 } else {
2091 i = menuPtr->numEntries-1;
2092 }
2093 } else if (i < 0) {
2094 i = -1;
2095 }
2096 *indexPtr = i;
2097 return TCL_OK;
2098 }
2099 Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
2100 }
2101
2102 for (i = 0; i < menuPtr->numEntries; i++) {
2103 char *label;
2104
2105 label = menuPtr->entries[i]->label;
2106 if ((label != NULL)
2107 && (Tcl_StringMatch(menuPtr->entries[i]->label, string))) {
2108 *indexPtr = i;
2109 return TCL_OK;
2110 }
2111 }
2112
2113 Tcl_AppendResult(interp, "bad menu entry index \"",
2114 string, "\"", (char *) NULL);
2115 return TCL_ERROR;
2116 }
2117
2118 /*
2119 *--------------------------------------------------------------
2120 *
2121 * MenuEventProc --
2122 *
2123 * This procedure is invoked by the Tk dispatcher for various
2124 * events on menus.
2125 *
2126 * Results:
2127 * None.
2128 *
2129 * Side effects:
2130 * When the window gets deleted, internal structures get
2131 * cleaned up. When it gets exposed, it is redisplayed.
2132 *
2133 *--------------------------------------------------------------
2134 */
2135
2136 static void
MenuEventProc(clientData,eventPtr)2137 MenuEventProc(clientData, eventPtr)
2138 ClientData clientData; /* Information about window. */
2139 XEvent *eventPtr; /* Information about event. */
2140 {
2141 Menu *menuPtr = (Menu *) clientData;
2142 if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
2143 EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
2144 } else if (eventPtr->type == ConfigureNotify) {
2145 EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
2146 } else if (eventPtr->type == DestroyNotify) {
2147 if (menuPtr->tkwin != NULL) {
2148 menuPtr->tkwin = NULL;
2149 Tcl_DeleteCommand(menuPtr->interp,
2150 Tcl_GetCommandName(menuPtr->interp, menuPtr->widgetCmd));
2151 }
2152 if (menuPtr->flags & REDRAW_PENDING) {
2153 Tcl_CancelIdleCall(DisplayMenu, (ClientData) menuPtr);
2154 }
2155 if (menuPtr->flags & RESIZE_PENDING) {
2156 Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
2157 }
2158 Tcl_EventuallyFree((ClientData) menuPtr, DestroyMenu);
2159 }
2160 }
2161
2162 /*
2163 *----------------------------------------------------------------------
2164 *
2165 * MenuCmdDeletedProc --
2166 *
2167 * This procedure is invoked when a widget command is deleted. If
2168 * the widget isn't already in the process of being destroyed,
2169 * this command destroys it.
2170 *
2171 * Results:
2172 * None.
2173 *
2174 * Side effects:
2175 * The widget is destroyed.
2176 *
2177 *----------------------------------------------------------------------
2178 */
2179
2180 static void
MenuCmdDeletedProc(clientData)2181 MenuCmdDeletedProc(clientData)
2182 ClientData clientData; /* Pointer to widget record for widget. */
2183 {
2184 Menu *menuPtr = (Menu *) clientData;
2185 Tk_Window tkwin = menuPtr->tkwin;
2186
2187 /*
2188 * This procedure could be invoked either because the window was
2189 * destroyed and the command was then deleted (in which case tkwin
2190 * is NULL) or because the command was deleted, and then this procedure
2191 * destroys the widget.
2192 */
2193
2194 if (tkwin != NULL) {
2195 menuPtr->tkwin = NULL;
2196 Tk_DestroyWindow(tkwin);
2197 }
2198 }
2199
2200 /*
2201 *----------------------------------------------------------------------
2202 *
2203 * MenuNewEntry --
2204 *
2205 * This procedure allocates and initializes a new menu entry.
2206 *
2207 * Results:
2208 * The return value is a pointer to a new menu entry structure,
2209 * which has been malloc-ed, initialized, and entered into the
2210 * entry array for the menu.
2211 *
2212 * Side effects:
2213 * Storage gets allocated.
2214 *
2215 *----------------------------------------------------------------------
2216 */
2217
2218 static MenuEntry *
MenuNewEntry(menuPtr,index,type)2219 MenuNewEntry(menuPtr, index, type)
2220 Menu *menuPtr; /* Menu that will hold the new entry. */
2221 int index; /* Where in the menu the new entry is to
2222 * go. */
2223 int type; /* The type of the new entry. */
2224 {
2225 MenuEntry *mePtr;
2226 MenuEntry **newEntries;
2227 int i;
2228
2229 /*
2230 * Create a new array of entries with an empty slot for the
2231 * new entry.
2232 */
2233
2234 newEntries = (MenuEntry **) ckalloc((unsigned)
2235 ((menuPtr->numEntries+1)*sizeof(MenuEntry *)));
2236 for (i = 0; i < index; i++) {
2237 newEntries[i] = menuPtr->entries[i];
2238 }
2239 for ( ; i < menuPtr->numEntries; i++) {
2240 newEntries[i+1] = menuPtr->entries[i];
2241 }
2242 if (menuPtr->numEntries != 0) {
2243 ckfree((char *) menuPtr->entries);
2244 }
2245 menuPtr->entries = newEntries;
2246 menuPtr->numEntries++;
2247 menuPtr->entries[index] = mePtr = (MenuEntry *) ckalloc(sizeof(MenuEntry));
2248 mePtr->type = type;
2249 mePtr->menuPtr = menuPtr;
2250 mePtr->label = NULL;
2251 mePtr->labelLength = 0;
2252 mePtr->underline = -1;
2253 mePtr->bitmap = None;
2254 mePtr->imageString = NULL;
2255 mePtr->image = NULL;
2256 mePtr->selectImageString = NULL;
2257 mePtr->selectImage = NULL;
2258 mePtr->accel = NULL;
2259 mePtr->accelLength = 0;
2260 mePtr->state = tkNormalUid;
2261 mePtr->height = 0;
2262 mePtr->y = 0;
2263 mePtr->indicatorDiameter = 0;
2264 mePtr->border = NULL;
2265 mePtr->fg = NULL;
2266 mePtr->activeBorder = NULL;
2267 mePtr->activeFg = NULL;
2268 mePtr->fontPtr = NULL;
2269 mePtr->textGC = None;
2270 mePtr->activeGC = None;
2271 mePtr->disabledGC = None;
2272 mePtr->indicatorOn = 1;
2273 mePtr->indicatorFg = NULL;
2274 mePtr->indicatorGC = None;
2275 mePtr->command = NULL;
2276 mePtr->name = NULL;
2277 mePtr->onValue = NULL;
2278 mePtr->offValue = NULL;
2279 mePtr->flags = 0;
2280 return mePtr;
2281 }
2282
2283 /*
2284 *----------------------------------------------------------------------
2285 *
2286 * MenuAddOrInsert --
2287 *
2288 * This procedure does all of the work of the "add" and "insert"
2289 * widget commands, allowing the code for these to be shared.
2290 *
2291 * Results:
2292 * A standard Tcl return value.
2293 *
2294 * Side effects:
2295 * A new menu entry is created in menuPtr.
2296 *
2297 *----------------------------------------------------------------------
2298 */
2299
2300 static int
MenuAddOrInsert(interp,menuPtr,indexString,argc,argv)2301 MenuAddOrInsert(interp, menuPtr, indexString, argc, argv)
2302 Tcl_Interp *interp; /* Used for error reporting. */
2303 Menu *menuPtr; /* Widget in which to create new
2304 * entry. */
2305 char *indexString; /* String describing index at which
2306 * to insert. NULL means insert at
2307 * end. */
2308 int argc; /* Number of elements in argv. */
2309 char **argv; /* Arguments to command: first arg
2310 * is type of entry, others are
2311 * config options. */
2312 {
2313 int c, type, i, index;
2314 size_t length;
2315 MenuEntry *mePtr;
2316
2317 if (indexString != NULL) {
2318 if (GetMenuIndex(interp, menuPtr, indexString, 1, &index) != TCL_OK) {
2319 return TCL_ERROR;
2320 }
2321 } else {
2322 index = menuPtr->numEntries;
2323 }
2324 if (index < 0) {
2325 Tcl_AppendResult(interp, "bad index \"", indexString, "\"",
2326 (char *) NULL);
2327 return TCL_ERROR;
2328 }
2329 if (menuPtr->tearOff && (index == 0)) {
2330 index = 1;
2331 }
2332
2333 /*
2334 * Figure out the type of the new entry.
2335 */
2336
2337 c = argv[0][0];
2338 length = strlen(argv[0]);
2339 if ((c == 'c') && (strncmp(argv[0], "cascade", length) == 0)
2340 && (length >= 2)) {
2341 type = CASCADE_ENTRY;
2342 } else if ((c == 'c') && (strncmp(argv[0], "checkbutton", length) == 0)
2343 && (length >= 2)) {
2344 type = CHECK_BUTTON_ENTRY;
2345 } else if ((c == 'c') && (strncmp(argv[0], "command", length) == 0)
2346 && (length >= 2)) {
2347 type = COMMAND_ENTRY;
2348 } else if ((c == 'r')
2349 && (strncmp(argv[0], "radiobutton", length) == 0)) {
2350 type = RADIO_BUTTON_ENTRY;
2351 } else if ((c == 's')
2352 && (strncmp(argv[0], "separator", length) == 0)) {
2353 type = SEPARATOR_ENTRY;
2354 } else {
2355 Tcl_AppendResult(interp, "bad menu entry type \"",
2356 argv[0], "\": must be cascade, checkbutton, ",
2357 "command, radiobutton, or separator", (char *) NULL);
2358 return TCL_ERROR;
2359 }
2360 mePtr = MenuNewEntry(menuPtr, index, type);
2361 if (ConfigureMenuEntry(interp, menuPtr, mePtr, index,
2362 argc-1, argv+1, 0) != TCL_OK) {
2363 DestroyMenuEntry((ClientData) mePtr);
2364 for (i = index+1; i < menuPtr->numEntries; i++) {
2365 menuPtr->entries[i-1] = menuPtr->entries[i];
2366 }
2367 menuPtr->numEntries--;
2368 return TCL_ERROR;
2369 }
2370 return TCL_OK;
2371 }
2372
2373 /*
2374 *--------------------------------------------------------------
2375 *
2376 * MenuVarProc --
2377 *
2378 * This procedure is invoked when someone changes the
2379 * state variable associated with a radiobutton or checkbutton
2380 * menu entry. The entry's selected state is set to match
2381 * the value of the variable.
2382 *
2383 * Results:
2384 * NULL is always returned.
2385 *
2386 * Side effects:
2387 * The menu entry may become selected or deselected.
2388 *
2389 *--------------------------------------------------------------
2390 */
2391
2392 /* ARGSUSED */
2393 static char *
MenuVarProc(clientData,interp,name1,name2,flags)2394 MenuVarProc(clientData, interp, name1, name2, flags)
2395 ClientData clientData; /* Information about menu entry. */
2396 Tcl_Interp *interp; /* Interpreter containing variable. */
2397 char *name1; /* First part of variable's name. */
2398 char *name2; /* Second part of variable's name. */
2399 int flags; /* Describes what just happened. */
2400 {
2401 MenuEntry *mePtr = (MenuEntry *) clientData;
2402 Menu *menuPtr;
2403 char *value;
2404
2405 menuPtr = mePtr->menuPtr;
2406
2407 /*
2408 * If the variable is being unset, then re-establish the
2409 * trace unless the whole interpreter is going away.
2410 */
2411
2412 if (flags & TCL_TRACE_UNSETS) {
2413 mePtr->flags &= ~ENTRY_SELECTED;
2414 if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
2415 Tcl_TraceVar(interp, mePtr->name,
2416 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
2417 MenuVarProc, clientData);
2418 }
2419 EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
2420 return (char *) NULL;
2421 }
2422
2423 /*
2424 * Use the value of the variable to update the selected status of
2425 * the menu entry.
2426 */
2427
2428 value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY);
2429 if (value == NULL) {
2430 value = "";
2431 }
2432 if (strcmp(value, mePtr->onValue) == 0) {
2433 if (mePtr->flags & ENTRY_SELECTED) {
2434 return (char *) NULL;
2435 }
2436 mePtr->flags |= ENTRY_SELECTED;
2437 } else if (mePtr->flags & ENTRY_SELECTED) {
2438 mePtr->flags &= ~ENTRY_SELECTED;
2439 } else {
2440 return (char *) NULL;
2441 }
2442 EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
2443 return (char *) NULL;
2444 }
2445
2446 /*
2447 *----------------------------------------------------------------------
2448 *
2449 * EventuallyRedrawMenu --
2450 *
2451 * Arrange for an entry of a menu, or the whole menu, to be
2452 * redisplayed at some point in the future.
2453 *
2454 * Results:
2455 * None.
2456 *
2457 * Side effects:
2458 * A when-idle hander is scheduled to do the redisplay, if there
2459 * isn't one already scheduled.
2460 *
2461 *----------------------------------------------------------------------
2462 */
2463
2464 static void
EventuallyRedrawMenu(menuPtr,mePtr)2465 EventuallyRedrawMenu(menuPtr, mePtr)
2466 register Menu *menuPtr; /* Information about menu to redraw. */
2467 register MenuEntry *mePtr; /* Entry to redraw. NULL means redraw
2468 * all the entries in the menu. */
2469 {
2470 int i;
2471 if (menuPtr->tkwin == NULL) {
2472 return;
2473 }
2474 if (mePtr != NULL) {
2475 mePtr->flags |= ENTRY_NEEDS_REDISPLAY;
2476 } else {
2477 for (i = 0; i < menuPtr->numEntries; i++) {
2478 menuPtr->entries[i]->flags |= ENTRY_NEEDS_REDISPLAY;
2479 }
2480 }
2481 if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)
2482 || (menuPtr->flags & REDRAW_PENDING)) {
2483 return;
2484 }
2485 Tcl_DoWhenIdle(DisplayMenu, (ClientData) menuPtr);
2486 menuPtr->flags |= REDRAW_PENDING;
2487 }
2488
2489 /*
2490 *--------------------------------------------------------------
2491 *
2492 * PostSubmenu --
2493 *
2494 * This procedure arranges for a particular submenu (i.e. the
2495 * menu corresponding to a given cascade entry) to be
2496 * posted.
2497 *
2498 * Results:
2499 * A standard Tcl return result. Errors may occur in the
2500 * Tcl commands generated to post and unpost submenus.
2501 *
2502 * Side effects:
2503 * If there is already a submenu posted, it is unposted.
2504 * The new submenu is then posted.
2505 *
2506 *--------------------------------------------------------------
2507 */
2508
2509 static int
PostSubmenu(interp,menuPtr,mePtr)2510 PostSubmenu(interp, menuPtr, mePtr)
2511 Tcl_Interp *interp; /* Used for invoking sub-commands and
2512 * reporting errors. */
2513 register Menu *menuPtr; /* Information about menu as a whole. */
2514 register MenuEntry *mePtr; /* Info about submenu that is to be
2515 * posted. NULL means make sure that
2516 * no submenu is posted. */
2517 {
2518 char string[30];
2519 int result, x, y;
2520 Tk_Window tkwin;
2521
2522 if (mePtr == menuPtr->postedCascade) {
2523 return TCL_OK;
2524 }
2525
2526 if (menuPtr->postedCascade != NULL) {
2527 /*
2528 * Note: when unposting a submenu, we have to redraw the entire
2529 * parent menu. This is because of a combination of the following
2530 * things:
2531 * (a) the submenu partially overlaps the parent.
2532 * (b) the submenu specifies "save under", which causes the X
2533 * server to make a copy of the information under it when it
2534 * is posted. When the submenu is unposted, the X server
2535 * copies this data back and doesn't generate any Expose
2536 * events for the parent.
2537 * (c) the parent may have redisplayed itself after the submenu
2538 * was posted, in which case the saved information is no
2539 * longer correct.
2540 * The simplest solution is just force a complete redisplay of
2541 * the parent.
2542 */
2543
2544 EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
2545 result = Tcl_VarEval(interp, menuPtr->postedCascade->name,
2546 " unpost", (char *) NULL);
2547 menuPtr->postedCascade = NULL;
2548 if (result != TCL_OK) {
2549 return result;
2550 }
2551 }
2552
2553 if ((mePtr != NULL) && (mePtr->name != NULL)
2554 && Tk_IsMapped(menuPtr->tkwin)) {
2555 /*
2556 * Make sure that the cascaded submenu is a child of the
2557 * parent menu.
2558 */
2559
2560 tkwin = Tk_NameToWindow(interp, mePtr->name, menuPtr->tkwin);
2561 if (tkwin == NULL) {
2562 return TCL_ERROR;
2563 }
2564 if (Tk_Parent(tkwin) != menuPtr->tkwin) {
2565 Tcl_AppendResult(interp, "cascaded sub-menu ",
2566 Tk_PathName(tkwin), " must be a child of ",
2567 Tk_PathName(menuPtr->tkwin), (char *) NULL);
2568 return TCL_ERROR;
2569 }
2570
2571 /*
2572 * Position the cascade with its upper left corner slightly
2573 * below and to the left of the upper right corner of the
2574 * menu entry (this is an attempt to match Motif behavior).
2575 */
2576 Tk_GetRootCoords(menuPtr->tkwin, &x, &y);
2577 x += Tk_Width(menuPtr->tkwin) - menuPtr->borderWidth
2578 - menuPtr->activeBorderWidth - 2;
2579 y += mePtr->y + menuPtr->activeBorderWidth + 2;
2580 sprintf(string, "%d %d", x, y);
2581 result = Tcl_VarEval(interp, mePtr->name, " post ", string,
2582 (char *) NULL);
2583 if (result != TCL_OK) {
2584 return result;
2585 }
2586 menuPtr->postedCascade = mePtr;
2587 }
2588 return TCL_OK;
2589 }
2590
2591 /*
2592 *----------------------------------------------------------------------
2593 *
2594 * ActivateMenuEntry --
2595 *
2596 * This procedure is invoked to make a particular menu entry
2597 * the active one, deactivating any other entry that might
2598 * currently be active.
2599 *
2600 * Results:
2601 * The return value is a standard Tcl result (errors can occur
2602 * while posting and unposting submenus).
2603 *
2604 * Side effects:
2605 * Menu entries get redisplayed, and the active entry changes.
2606 * Submenus may get posted and unposted.
2607 *
2608 *----------------------------------------------------------------------
2609 */
2610
2611 static int
ActivateMenuEntry(menuPtr,index)2612 ActivateMenuEntry(menuPtr, index)
2613 register Menu *menuPtr; /* Menu in which to activate. */
2614 int index; /* Index of entry to activate, or
2615 * -1 to deactivate all entries. */
2616 {
2617 register MenuEntry *mePtr;
2618 int result = TCL_OK;
2619
2620 if (menuPtr->active >= 0) {
2621 mePtr = menuPtr->entries[menuPtr->active];
2622
2623 /*
2624 * Don't change the state unless it's currently active (state
2625 * might already have been changed to disabled).
2626 */
2627
2628 if (mePtr->state == tkActiveUid) {
2629 mePtr->state = tkNormalUid;
2630 }
2631 EventuallyRedrawMenu(menuPtr, menuPtr->entries[menuPtr->active]);
2632 }
2633 menuPtr->active = index;
2634 if (index >= 0) {
2635 mePtr = menuPtr->entries[index];
2636 mePtr->state = tkActiveUid;
2637 EventuallyRedrawMenu(menuPtr, mePtr);
2638 }
2639 return result;
2640 }
2641
2642 /*
2643 *----------------------------------------------------------------------
2644 *
2645 * MenuImageProc --
2646 *
2647 * This procedure is invoked by the image code whenever the manager
2648 * for an image does something that affects the size of contents
2649 * of an image displayed in a menu entry.
2650 *
2651 * Results:
2652 * None.
2653 *
2654 * Side effects:
2655 * Arranges for the menu to get redisplayed.
2656 *
2657 *----------------------------------------------------------------------
2658 */
2659
2660 static void
MenuImageProc(clientData,x,y,width,height,imgWidth,imgHeight)2661 MenuImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
2662 ClientData clientData; /* Pointer to widget record. */
2663 int x, y; /* Upper left pixel (within image)
2664 * that must be redisplayed. */
2665 int width, height; /* Dimensions of area to redisplay
2666 * (may be <= 0). */
2667 int imgWidth, imgHeight; /* New dimensions of image. */
2668 {
2669 register Menu *menuPtr = ((MenuEntry *) clientData)->menuPtr;
2670
2671 if ((menuPtr->tkwin != NULL) && !(menuPtr->flags & RESIZE_PENDING)) {
2672 menuPtr->flags |= RESIZE_PENDING;
2673 Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
2674 }
2675 }
2676
2677 /*
2678 *----------------------------------------------------------------------
2679 *
2680 * MenuSelectImageProc --
2681 *
2682 * This procedure is invoked by the image code whenever the manager
2683 * for an image does something that affects the size of contents
2684 * of an image displayed in a menu entry when it is selected.
2685 *
2686 * Results:
2687 * None.
2688 *
2689 * Side effects:
2690 * Arranges for the menu to get redisplayed.
2691 *
2692 *----------------------------------------------------------------------
2693 */
2694
2695 static void
MenuSelectImageProc(clientData,x,y,width,height,imgWidth,imgHeight)2696 MenuSelectImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
2697 ClientData clientData; /* Pointer to widget record. */
2698 int x, y; /* Upper left pixel (within image)
2699 * that must be redisplayed. */
2700 int width, height; /* Dimensions of area to redisplay
2701 * (may be <= 0). */
2702 int imgWidth, imgHeight; /* New dimensions of image. */
2703 {
2704 register MenuEntry *mePtr = (MenuEntry *) clientData;
2705
2706 if ((mePtr->flags & ENTRY_SELECTED)
2707 && !(mePtr->menuPtr->flags & REDRAW_PENDING)) {
2708 mePtr->menuPtr->flags |= REDRAW_PENDING;
2709 Tcl_DoWhenIdle(DisplayMenu, (ClientData) mePtr->menuPtr);
2710 }
2711 }
2712