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