Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/lib/libtk/generic/tkMenu.c
1810 views
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
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
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
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
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
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
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
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
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
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
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
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 *
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
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 *
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
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
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
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
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
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
2713