diff options
author | William Joye <wjoye@cfa.harvard.edu> | 2017-10-17 19:51:44 (GMT) |
---|---|---|
committer | William Joye <wjoye@cfa.harvard.edu> | 2017-10-17 19:51:44 (GMT) |
commit | 9bfb1e415c87790341c6a3520b081292fcdb058b (patch) | |
tree | 1a2c8954ffef1f09bd97c585c5f289046497712b /tk8.6/generic/tkMenu.c | |
parent | 9b7a6c3507ea3383c60aaecb29f873c9b590ccca (diff) | |
parent | 991debcf36ad518e7e9a53b3ad3a388713ffdc1a (diff) | |
download | blt-9bfb1e415c87790341c6a3520b081292fcdb058b.zip blt-9bfb1e415c87790341c6a3520b081292fcdb058b.tar.gz blt-9bfb1e415c87790341c6a3520b081292fcdb058b.tar.bz2 |
Merge commit '991debcf36ad518e7e9a53b3ad3a388713ffdc1a' as 'tk8.6'
Diffstat (limited to 'tk8.6/generic/tkMenu.c')
-rw-r--r-- | tk8.6/generic/tkMenu.c | 3611 |
1 files changed, 3611 insertions, 0 deletions
diff --git a/tk8.6/generic/tkMenu.c b/tk8.6/generic/tkMenu.c new file mode 100644 index 0000000..d24516f --- /dev/null +++ b/tk8.6/generic/tkMenu.c @@ -0,0 +1,3611 @@ +/* + * tkMenu.c -- + * + * This file contains most of the code for implementing menus in Tk. It takes + * care of all of the generic (platform-independent) parts of menus, and is + * supplemented by platform-specific files. The geometry calculation and + * drawing code for menus is in the file tkMenuDraw.c + * + * Copyright (c) 1990-1994 The Regents of the University of California. + * Copyright (c) 1994-1998 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +/* + * Notes on implementation of menus: + * + * Menus can be used in three ways: + * - as a popup menu, either as part of a menubutton or standalone. + * - as a menubar. The menu's cascade items are arranged according to the + * specific platform to provide the user access to the menus at all times + * - as a tearoff palette. This is a window with the menu's items in it. + * + * The goal is to provide the Tk developer with a way to use a common set of + * menus for all of these tasks. + * + * In order to make the bindings for cascade menus work properly under Unix, + * the cascade menus' pathnames must be proper children of the menu that they + * are cascade from. So if there is a menu .m, and it has two cascades + * labelled "File" and "Edit", the cascade menus might have the pathnames + * .m.file and .m.edit. Another constraint is that the menus used for menubars + * must be children of the toplevel widget that they are attached to. And on + * the Macintosh, the platform specific menu handle for cascades attached to a + * menu bar must have a title that matches the label for the cascade menu. + * + * To handle all of the constraints, Tk menubars and tearoff menus are + * implemented using menu clones. Menu clones are full menus in their own + * right; they have a Tk window and pathname associated with them; they have a + * TkMenu structure and array of entries. However, they are linked with the + * original menu that they were cloned from. The reflect the attributes of the + * original, or "master", menu. So if an item is added to a menu, and that + * menu has clones, then the item must be added to all of its clones also. + * Menus are cloned when a menu is torn-off or when a menu is assigned as a + * menubar using the "-menu" option of the toplevel's pathname configure + * subcommand. When a clone is destroyed, only the clone is destroyed, but + * when the master menu is destroyed, all clones are also destroyed. This + * allows the developer to just deal with one set of menus when creating and + * destroying. + * + * Clones are rather tricky when a menu with cascade entries is cloned (such + * as a menubar). Not only does the menu have to be cloned, but each cascade + * entry's corresponding menu must also be cloned. This maintains the pathname + * parent-child hierarchy necessary for menubars and toplevels to work. This + * leads to several special cases: + * + * 1. When a new menu is created, and it is pointed to by cascade entries in + * cloned menus, the new menu has to be cloned to parallel the cascade + * structure. + * 2. When a cascade item is added to a menu that has been cloned, and the + * menu that the cascade item points to exists, that menu has to be cloned. + * 3. When the menu that a cascade entry points to is changed, the old cloned + * cascade menu has to be discarded, and the new one has to be cloned. + */ + +#if 0 + +/* + * used only to test for old config code + */ + +#define __NO_OLD_CONFIG +#endif + +#include "tkInt.h" +#include "tkMenu.h" + +#define MENU_HASH_KEY "tkMenus" + +typedef struct ThreadSpecificData { + int menusInitialized; /* Flag indicates whether thread-specific + * elements of the Windows Menu module have + * been initialized. */ + Tk_OptionTable menuOptionTable; + /* The option table for menus. */ + Tk_OptionTable entryOptionTables[6]; + /* The tables for menu entries. */ +} ThreadSpecificData; +static Tcl_ThreadDataKey dataKey; + +/* + * The following flag indicates whether the process-wide state for the Menu + * module has been intialized. The Mutex protects access to that flag. + */ + +static int menusInitialized; +TCL_DECLARE_MUTEX(menuMutex) + +/* + * Configuration specs for individual menu entries. If this changes, be sure + * to update code in TkpMenuInit that changes the font string entry. + */ + +static const char *const menuStateStrings[] = {"active", "normal", "disabled", NULL}; + +static const char *const menuEntryTypeStrings[] = { + "cascade", "checkbutton", "command", "radiobutton", "separator", NULL +}; + +/* + * The following table defines the legal values for the -compound option. It + * is used with the "enum compound" declaration in tkMenu.h + */ + +static const char *const compoundStrings[] = { + "bottom", "center", "left", "none", "right", "top", NULL +}; + +static const Tk_OptionSpec tkBasicMenuEntryConfigSpecs[] = { + {TK_OPTION_BORDER, "-activebackground", NULL, NULL, + DEF_MENU_ENTRY_ACTIVE_BG, Tk_Offset(TkMenuEntry, activeBorderPtr), -1, + TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_COLOR, "-activeforeground", NULL, NULL, + DEF_MENU_ENTRY_ACTIVE_FG, + Tk_Offset(TkMenuEntry, activeFgPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_STRING, "-accelerator", NULL, NULL, + DEF_MENU_ENTRY_ACCELERATOR, + Tk_Offset(TkMenuEntry, accelPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_BORDER, "-background", NULL, NULL, + DEF_MENU_ENTRY_BG, + Tk_Offset(TkMenuEntry, borderPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_BITMAP, "-bitmap", NULL, NULL, + DEF_MENU_ENTRY_BITMAP, + Tk_Offset(TkMenuEntry, bitmapPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_BOOLEAN, "-columnbreak", NULL, NULL, + DEF_MENU_ENTRY_COLUMN_BREAK, + -1, Tk_Offset(TkMenuEntry, columnBreak), 0, NULL, 0}, + {TK_OPTION_STRING, "-command", NULL, NULL, + DEF_MENU_ENTRY_COMMAND, + Tk_Offset(TkMenuEntry, commandPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_STRING_TABLE, "-compound", "compound", "Compound", + DEF_MENU_ENTRY_COMPOUND, -1, Tk_Offset(TkMenuEntry, compound), 0, + (ClientData) compoundStrings, 0}, + {TK_OPTION_FONT, "-font", NULL, NULL, + DEF_MENU_ENTRY_FONT, + Tk_Offset(TkMenuEntry, fontPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_COLOR, "-foreground", NULL, NULL, + DEF_MENU_ENTRY_FG, + Tk_Offset(TkMenuEntry, fgPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_BOOLEAN, "-hidemargin", NULL, NULL, + DEF_MENU_ENTRY_HIDE_MARGIN, + -1, Tk_Offset(TkMenuEntry, hideMargin), 0, NULL, 0}, + {TK_OPTION_STRING, "-image", NULL, NULL, + DEF_MENU_ENTRY_IMAGE, + Tk_Offset(TkMenuEntry, imagePtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_STRING, "-label", NULL, NULL, + DEF_MENU_ENTRY_LABEL, + Tk_Offset(TkMenuEntry, labelPtr), -1, 0, NULL, 0}, + {TK_OPTION_STRING_TABLE, "-state", NULL, NULL, + DEF_MENU_ENTRY_STATE, + -1, Tk_Offset(TkMenuEntry, state), 0, + (ClientData) menuStateStrings, 0}, + {TK_OPTION_INT, "-underline", NULL, NULL, + DEF_MENU_ENTRY_UNDERLINE, -1, Tk_Offset(TkMenuEntry, underline), 0, NULL, 0}, + {TK_OPTION_END, NULL, NULL, NULL, 0, 0, 0, 0, NULL, 0} +}; + +static const Tk_OptionSpec tkSeparatorEntryConfigSpecs[] = { + {TK_OPTION_BORDER, "-background", NULL, NULL, + DEF_MENU_ENTRY_BG, + Tk_Offset(TkMenuEntry, borderPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_END, NULL, NULL, NULL, 0, 0, 0, 0, NULL, 0} +}; + +static const Tk_OptionSpec tkCheckButtonEntryConfigSpecs[] = { + {TK_OPTION_BOOLEAN, "-indicatoron", NULL, NULL, + DEF_MENU_ENTRY_INDICATOR, + -1, Tk_Offset(TkMenuEntry, indicatorOn), 0, NULL, 0}, + {TK_OPTION_STRING, "-offvalue", NULL, NULL, + DEF_MENU_ENTRY_OFF_VALUE, + Tk_Offset(TkMenuEntry, offValuePtr), -1, 0, NULL, 0}, + {TK_OPTION_STRING, "-onvalue", NULL, NULL, + DEF_MENU_ENTRY_ON_VALUE, + Tk_Offset(TkMenuEntry, onValuePtr), -1, 0, NULL, 0}, + {TK_OPTION_COLOR, "-selectcolor", NULL, NULL, + DEF_MENU_ENTRY_SELECT, + Tk_Offset(TkMenuEntry, indicatorFgPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_STRING, "-selectimage", NULL, NULL, + DEF_MENU_ENTRY_SELECT_IMAGE, + Tk_Offset(TkMenuEntry, selectImagePtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_STRING, "-variable", NULL, NULL, + DEF_MENU_ENTRY_CHECK_VARIABLE, + Tk_Offset(TkMenuEntry, namePtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_END, NULL, NULL, NULL, + NULL, 0, -1, 0, tkBasicMenuEntryConfigSpecs, 0} +}; + +static const Tk_OptionSpec tkRadioButtonEntryConfigSpecs[] = { + {TK_OPTION_BOOLEAN, "-indicatoron", NULL, NULL, + DEF_MENU_ENTRY_INDICATOR, + -1, Tk_Offset(TkMenuEntry, indicatorOn), 0, NULL, 0}, + {TK_OPTION_COLOR, "-selectcolor", NULL, NULL, + DEF_MENU_ENTRY_SELECT, + Tk_Offset(TkMenuEntry, indicatorFgPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_STRING, "-selectimage", NULL, NULL, + DEF_MENU_ENTRY_SELECT_IMAGE, + Tk_Offset(TkMenuEntry, selectImagePtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_STRING, "-value", NULL, NULL, + DEF_MENU_ENTRY_VALUE, + Tk_Offset(TkMenuEntry, onValuePtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_STRING, "-variable", NULL, NULL, + DEF_MENU_ENTRY_RADIO_VARIABLE, + Tk_Offset(TkMenuEntry, namePtr), -1, 0, NULL, 0}, + {TK_OPTION_END, NULL, NULL, NULL, + NULL, 0, -1, 0, tkBasicMenuEntryConfigSpecs, 0} +}; + +static const Tk_OptionSpec tkCascadeEntryConfigSpecs[] = { + {TK_OPTION_STRING, "-menu", NULL, NULL, + DEF_MENU_ENTRY_MENU, + Tk_Offset(TkMenuEntry, namePtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_END, NULL, NULL, NULL, + NULL, 0, -1, 0, tkBasicMenuEntryConfigSpecs, 0} +}; + +static const Tk_OptionSpec tkTearoffEntryConfigSpecs[] = { + {TK_OPTION_BORDER, "-background", NULL, NULL, + DEF_MENU_ENTRY_BG, + Tk_Offset(TkMenuEntry, borderPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_STRING_TABLE, "-state", NULL, NULL, + DEF_MENU_ENTRY_STATE, -1, Tk_Offset(TkMenuEntry, state), 0, + (ClientData) menuStateStrings, 0}, + {TK_OPTION_END, NULL, NULL, NULL, 0, 0, 0, 0, NULL, 0} +}; + +static const Tk_OptionSpec *specsArray[] = { + tkCascadeEntryConfigSpecs, tkCheckButtonEntryConfigSpecs, + tkBasicMenuEntryConfigSpecs, tkRadioButtonEntryConfigSpecs, + tkSeparatorEntryConfigSpecs, tkTearoffEntryConfigSpecs +}; + +/* + * Menu type strings for use with Tcl_GetIndexFromObj. + */ + +static const char *const menuTypeStrings[] = { + "normal", "tearoff", "menubar", NULL +}; + +static const Tk_OptionSpec tkMenuConfigSpecs[] = { + {TK_OPTION_BORDER, "-activebackground", "activeBackground", + "Foreground", DEF_MENU_ACTIVE_BG_COLOR, + Tk_Offset(TkMenu, activeBorderPtr), -1, 0, + (ClientData) DEF_MENU_ACTIVE_BG_MONO, 0}, + {TK_OPTION_PIXELS, "-activeborderwidth", "activeBorderWidth", + "BorderWidth", DEF_MENU_ACTIVE_BORDER_WIDTH, + Tk_Offset(TkMenu, activeBorderWidthPtr), -1, 0, NULL, 0}, + {TK_OPTION_COLOR, "-activeforeground", "activeForeground", + "Background", DEF_MENU_ACTIVE_FG_COLOR, + Tk_Offset(TkMenu, activeFgPtr), -1, 0, + (ClientData) DEF_MENU_ACTIVE_FG_MONO, 0}, + {TK_OPTION_BORDER, "-background", "background", "Background", + DEF_MENU_BG_COLOR, Tk_Offset(TkMenu, borderPtr), -1, 0, + (ClientData) DEF_MENU_BG_MONO, 0}, + {TK_OPTION_SYNONYM, "-bd", NULL, NULL, + NULL, 0, -1, 0, "-borderwidth", 0}, + {TK_OPTION_SYNONYM, "-bg", NULL, NULL, + NULL, 0, -1, 0, "-background", 0}, + {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", + DEF_MENU_BORDER_WIDTH, + Tk_Offset(TkMenu, borderWidthPtr), -1, 0, NULL, 0}, + {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor", + DEF_MENU_CURSOR, + Tk_Offset(TkMenu, cursorPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_COLOR, "-disabledforeground", "disabledForeground", + "DisabledForeground", DEF_MENU_DISABLED_FG_COLOR, + Tk_Offset(TkMenu, disabledFgPtr), -1, TK_OPTION_NULL_OK, + (ClientData) DEF_MENU_DISABLED_FG_MONO, 0}, + {TK_OPTION_SYNONYM, "-fg", NULL, NULL, + NULL, 0, -1, 0, "-foreground", 0}, + {TK_OPTION_FONT, "-font", "font", "Font", + DEF_MENU_FONT, Tk_Offset(TkMenu, fontPtr), -1, 0, NULL, 0}, + {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground", + DEF_MENU_FG, Tk_Offset(TkMenu, fgPtr), -1, 0, NULL, 0}, + {TK_OPTION_STRING, "-postcommand", "postCommand", "Command", + DEF_MENU_POST_COMMAND, + Tk_Offset(TkMenu, postCommandPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_RELIEF, "-relief", "relief", "Relief", + DEF_MENU_RELIEF, Tk_Offset(TkMenu, reliefPtr), -1, 0, NULL, 0}, + {TK_OPTION_COLOR, "-selectcolor", "selectColor", "Background", + DEF_MENU_SELECT_COLOR, Tk_Offset(TkMenu, indicatorFgPtr), -1, 0, + (ClientData) DEF_MENU_SELECT_MONO, 0}, + {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus", + DEF_MENU_TAKE_FOCUS, + Tk_Offset(TkMenu, takeFocusPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_BOOLEAN, "-tearoff", "tearOff", "TearOff", + DEF_MENU_TEAROFF, -1, Tk_Offset(TkMenu, tearoff), 0, NULL, 0}, + {TK_OPTION_STRING, "-tearoffcommand", "tearOffCommand", + "TearOffCommand", DEF_MENU_TEAROFF_CMD, + Tk_Offset(TkMenu, tearoffCommandPtr), -1, TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_STRING, "-title", "title", "Title", + DEF_MENU_TITLE, Tk_Offset(TkMenu, titlePtr), -1, + TK_OPTION_NULL_OK, NULL, 0}, + {TK_OPTION_STRING_TABLE, "-type", "type", "Type", + DEF_MENU_TYPE, Tk_Offset(TkMenu, menuTypePtr), -1, TK_OPTION_NULL_OK, + (ClientData) menuTypeStrings, 0}, + {TK_OPTION_END, NULL, NULL, NULL, 0, 0, 0, 0, NULL, 0} +}; + +/* + * Command line options. Put here because MenuCmd has to look at them along + * with MenuWidgetObjCmd. + */ + +static const char *const menuOptions[] = { + "activate", "add", "cget", "clone", "configure", "delete", "entrycget", + "entryconfigure", "index", "insert", "invoke", "post", "postcascade", + "type", "unpost", "xposition", "yposition", NULL +}; +enum options { + MENU_ACTIVATE, MENU_ADD, MENU_CGET, MENU_CLONE, MENU_CONFIGURE, + MENU_DELETE, MENU_ENTRYCGET, MENU_ENTRYCONFIGURE, MENU_INDEX, + MENU_INSERT, MENU_INVOKE, MENU_POST, MENU_POSTCASCADE, MENU_TYPE, + MENU_UNPOST, MENU_XPOSITION, MENU_YPOSITION +}; + +/* + * Prototypes for static functions in this file: + */ + +static int CloneMenu(TkMenu *menuPtr, Tcl_Obj *newMenuName, + Tcl_Obj *newMenuTypeString); +static int ConfigureMenu(Tcl_Interp *interp, TkMenu *menuPtr, + int objc, Tcl_Obj *const objv[]); +static int ConfigureMenuCloneEntries(Tcl_Interp *interp, + TkMenu *menuPtr, int index, + int objc, Tcl_Obj *const objv[]); +static int ConfigureMenuEntry(TkMenuEntry *mePtr, + int objc, Tcl_Obj *const objv[]); +static void DeleteMenuCloneEntries(TkMenu *menuPtr, + int first, int last); +static void DestroyMenuHashTable(ClientData clientData, + Tcl_Interp *interp); +static void DestroyMenuInstance(TkMenu *menuPtr); +static void DestroyMenuEntry(void *memPtr); +static int GetIndexFromCoords(Tcl_Interp *interp, + TkMenu *menuPtr, const char *string, + int *indexPtr); +static int MenuDoYPosition(Tcl_Interp *interp, + TkMenu *menuPtr, Tcl_Obj *objPtr); +static int MenuDoXPosition(Tcl_Interp *interp, + TkMenu *menuPtr, Tcl_Obj *objPtr); +static int MenuAddOrInsert(Tcl_Interp *interp, + TkMenu *menuPtr, Tcl_Obj *indexPtr, int objc, + Tcl_Obj *const objv[]); +static void MenuCmdDeletedProc(ClientData clientData); +static TkMenuEntry * MenuNewEntry(TkMenu *menuPtr, int index, int type); +static char * MenuVarProc(ClientData clientData, + Tcl_Interp *interp, const char *name1, + const char *name2, int flags); +static int MenuWidgetObjCmd(ClientData clientData, + Tcl_Interp *interp, int objc, + Tcl_Obj *const objv[]); +static void MenuWorldChanged(ClientData instanceData); +static int PostProcessEntry(TkMenuEntry *mePtr); +static void RecursivelyDeleteMenu(TkMenu *menuPtr); +static void UnhookCascadeEntry(TkMenuEntry *mePtr); +static void TkMenuCleanup(ClientData unused); + +/* + * The structure below is a list of procs that respond to certain window + * manager events. One of these includes a font change, which forces the + * geometry proc to be called. + */ + +static const Tk_ClassProcs menuClass = { + sizeof(Tk_ClassProcs), /* size */ + MenuWorldChanged, /* worldChangedProc */ + NULL, /* createProc */ + NULL /* modalProc */ +}; + +/* + *-------------------------------------------------------------- + * + * Tk_MenuObjCmd -- + * + * This function is invoked to process the "menu" Tcl command. See the + * user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +int +Tk_MenuObjCmd( + ClientData clientData, /* Main window associated with interpreter. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument strings. */ +{ + Tk_Window tkwin = clientData; + Tk_Window newWin; + register TkMenu *menuPtr; + TkMenuReferences *menuRefPtr; + int i, index, toplevel; + const char *windowName; + static const char *const typeStringList[] = {"-type", NULL}; + ThreadSpecificData *tsdPtr = + Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "pathName ?-option value ...?"); + return TCL_ERROR; + } + + TkMenuInit(); + + toplevel = 1; + for (i = 2; i < (objc - 1); i++) { + if (Tcl_GetIndexFromObjStruct(NULL, objv[i], typeStringList, + sizeof(char *), NULL, 0, &index) != TCL_ERROR) { + if ((Tcl_GetIndexFromObjStruct(NULL, objv[i + 1], menuTypeStrings, + sizeof(char *), NULL, 0, &index) == TCL_OK) && (index == MENUBAR)) { + toplevel = 0; + } + break; + } + } + + windowName = Tcl_GetString(objv[1]); + newWin = Tk_CreateWindowFromPath(interp, tkwin, windowName, + toplevel ? "" : NULL); + if (newWin == NULL) { + return TCL_ERROR; + } + + /* + * Initialize the data structure for the menu. Note that the menuPtr is + * eventually freed in 'TkMenuEventProc' in tkMenuDraw.c, when + * Tcl_EventuallyFree is called. + */ + + menuPtr = ckalloc(sizeof(TkMenu)); + memset(menuPtr, 0, sizeof(TkMenu)); + menuPtr->tkwin = newWin; + menuPtr->display = Tk_Display(newWin); + menuPtr->interp = interp; + menuPtr->widgetCmd = Tcl_CreateObjCommand(interp, + Tk_PathName(menuPtr->tkwin), MenuWidgetObjCmd, menuPtr, + MenuCmdDeletedProc); + menuPtr->active = -1; + menuPtr->cursorPtr = None; + menuPtr->masterMenuPtr = menuPtr; + menuPtr->menuType = UNKNOWN_TYPE; + TkMenuInitializeDrawingFields(menuPtr); + + Tk_SetClass(menuPtr->tkwin, "Menu"); + Tk_SetClassProcs(menuPtr->tkwin, &menuClass, menuPtr); + Tk_CreateEventHandler(newWin, + ExposureMask|StructureNotifyMask|ActivateMask, + TkMenuEventProc, menuPtr); + if (Tk_InitOptions(interp, (char *) menuPtr, + tsdPtr->menuOptionTable, menuPtr->tkwin) + != TCL_OK) { + Tk_DestroyWindow(menuPtr->tkwin); + return TCL_ERROR; + } + + + menuRefPtr = TkCreateMenuReferences(menuPtr->interp, + Tk_PathName(menuPtr->tkwin)); + menuRefPtr->menuPtr = menuPtr; + menuPtr->menuRefPtr = menuRefPtr; + if (TCL_OK != TkpNewMenu(menuPtr)) { + Tk_DestroyWindow(menuPtr->tkwin); + return TCL_ERROR; + } + + if (ConfigureMenu(interp, menuPtr, objc - 2, objv + 2) != TCL_OK) { + Tk_DestroyWindow(menuPtr->tkwin); + return TCL_ERROR; + } + + /* + * If a menu has a parent menu pointing to it as a cascade entry, the + * parent menu needs to be told that this menu now exists so that the + * platform-part of the menu is correctly updated. + * + * If a menu has an instance and has cascade entries, then each cascade + * menu must also have a parallel instance. This is especially true on the + * Mac, where each menu has to have a separate title everytime it is in a + * menubar. For instance, say you have a menu .m1 with a cascade entry for + * .m2, where .m2 does not exist yet. You then put .m1 into a menubar. + * This creates a menubar instance for .m1, but since .m2 is not there, + * nothing else happens. When we go to create .m2, we hook it up properly + * with .m1. However, we now need to clone .m2 and assign the clone of .m2 + * to be the cascade entry for the clone of .m1. This is special case #1 + * listed in the introductory comment. + */ + + if (menuRefPtr->parentEntryPtr != NULL) { + TkMenuEntry *cascadeListPtr = menuRefPtr->parentEntryPtr; + TkMenuEntry *nextCascadePtr; + Tcl_Obj *newMenuName, *newObjv[2]; + + while (cascadeListPtr != NULL) { + nextCascadePtr = cascadeListPtr->nextCascadePtr; + + /* + * If we have a new master menu, and an existing cloned menu + * points to this menu in a cascade entry, we have to clone the + * new menu and point the entry to the clone instead of the menu + * we are creating. Otherwise, ConfigureMenuEntry will hook up the + * platform-specific cascade linkages now that the menu we are + * creating exists. + */ + + if ((menuPtr->masterMenuPtr != menuPtr) + || ((menuPtr->masterMenuPtr == menuPtr) + && ((cascadeListPtr->menuPtr->masterMenuPtr + == cascadeListPtr->menuPtr)))) { + newObjv[0] = Tcl_NewStringObj("-menu", -1); + newObjv[1] = Tcl_NewStringObj(Tk_PathName(menuPtr->tkwin),-1); + Tcl_IncrRefCount(newObjv[0]); + Tcl_IncrRefCount(newObjv[1]); + ConfigureMenuEntry(cascadeListPtr, 2, newObjv); + Tcl_DecrRefCount(newObjv[0]); + Tcl_DecrRefCount(newObjv[1]); + } else { + Tcl_Obj *normalPtr = Tcl_NewStringObj("normal", -1); + Tcl_Obj *windowNamePtr = Tcl_NewStringObj( + Tk_PathName(cascadeListPtr->menuPtr->tkwin), -1); + + Tcl_IncrRefCount(normalPtr); + Tcl_IncrRefCount(windowNamePtr); + newMenuName = TkNewMenuName(menuPtr->interp, + windowNamePtr, menuPtr); + Tcl_IncrRefCount(newMenuName); + CloneMenu(menuPtr, newMenuName, normalPtr); + + /* + * Now we can set the new menu instance to be the cascade + * entry of the parent's instance. + */ + + newObjv[0] = Tcl_NewStringObj("-menu", -1); + newObjv[1] = newMenuName; + Tcl_IncrRefCount(newObjv[0]); + ConfigureMenuEntry(cascadeListPtr, 2, newObjv); + Tcl_DecrRefCount(normalPtr); + Tcl_DecrRefCount(newObjv[0]); + Tcl_DecrRefCount(newObjv[1]); + Tcl_DecrRefCount(windowNamePtr); + } + cascadeListPtr = nextCascadePtr; + } + } + + /* + * If there already exist toplevel widgets that refer to this menu, find + * them and notify them so that they can reconfigure their geometry to + * reflect the menu. + */ + + if (menuRefPtr->topLevelListPtr != NULL) { + TkMenuTopLevelList *topLevelListPtr = menuRefPtr->topLevelListPtr; + TkMenuTopLevelList *nextPtr; + Tk_Window listtkwin; + + while (topLevelListPtr != NULL) { + /* + * Need to get the next pointer first. TkSetWindowMenuBar changes + * the list, so that the next pointer is different after calling + * it. + */ + + nextPtr = topLevelListPtr->nextPtr; + listtkwin = topLevelListPtr->tkwin; + TkSetWindowMenuBar(menuPtr->interp, listtkwin, + Tk_PathName(menuPtr->tkwin), Tk_PathName(menuPtr->tkwin)); + topLevelListPtr = nextPtr; + } + } + + Tcl_SetObjResult(interp, TkNewWindowObj(menuPtr->tkwin)); + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * MenuWidgetObjCmd -- + * + * This function is invoked to process the Tcl command that corresponds + * to a widget managed by this module. See the user documentation for + * details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +static int +MenuWidgetObjCmd( + ClientData clientData, /* Information about menu widget. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument strings. */ +{ + register TkMenu *menuPtr = clientData; + register TkMenuEntry *mePtr; + int result = TCL_OK; + int option; + ThreadSpecificData *tsdPtr = + Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); + return TCL_ERROR; + } + if (Tcl_GetIndexFromObjStruct(interp, objv[1], menuOptions, + sizeof(char *), "option", 0, &option) != TCL_OK) { + return TCL_ERROR; + } + Tcl_Preserve(menuPtr); + + switch ((enum options) option) { + case MENU_ACTIVATE: { + int index; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); + goto error; + } + if (TkGetMenuIndex(interp, menuPtr, objv[2], 0, &index) != TCL_OK) { + goto error; + } + if (menuPtr->active == index) { + goto done; + } + if ((index >= 0) && ((menuPtr->entries[index]->type==SEPARATOR_ENTRY) + || (menuPtr->entries[index]->state == ENTRY_DISABLED))) { + index = -1; + } + result = TkActivateMenuEntry(menuPtr, index); + break; + } + case MENU_ADD: + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "type ?-option value ...?"); + goto error; + } + + if (MenuAddOrInsert(interp, menuPtr, NULL, objc-2, objv+2) != TCL_OK){ + goto error; + } + break; + case MENU_CGET: { + Tcl_Obj *resultPtr; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "option"); + goto error; + } + resultPtr = Tk_GetOptionValue(interp, (char *) menuPtr, + tsdPtr->menuOptionTable, objv[2], + menuPtr->tkwin); + if (resultPtr == NULL) { + goto error; + } + Tcl_SetObjResult(interp, resultPtr); + break; + } + case MENU_CLONE: + if ((objc < 3) || (objc > 4)) { + Tcl_WrongNumArgs(interp, 2, objv, "newMenuName ?menuType?"); + goto error; + } + result = CloneMenu(menuPtr, objv[2], (objc == 3) ? NULL : objv[3]); + break; + case MENU_CONFIGURE: { + Tcl_Obj *resultPtr; + + if (objc == 2) { + resultPtr = Tk_GetOptionInfo(interp, (char *) menuPtr, + tsdPtr->menuOptionTable, NULL, + menuPtr->tkwin); + if (resultPtr == NULL) { + result = TCL_ERROR; + } else { + result = TCL_OK; + Tcl_SetObjResult(interp, resultPtr); + } + } else if (objc == 3) { + resultPtr = Tk_GetOptionInfo(interp, (char *) menuPtr, + tsdPtr->menuOptionTable, objv[2], + menuPtr->tkwin); + if (resultPtr == NULL) { + result = TCL_ERROR; + } else { + result = TCL_OK; + Tcl_SetObjResult(interp, resultPtr); + } + } else { + result = ConfigureMenu(interp, menuPtr, objc - 2, objv + 2); + } + if (result != TCL_OK) { + goto error; + } + break; + } + case MENU_DELETE: { + int first, last; + + if ((objc != 3) && (objc != 4)) { + Tcl_WrongNumArgs(interp, 2, objv, "first ?last?"); + goto error; + } + + /* + * If 'first' explicitly refers to past the end of the menu, we don't + * do anything. [Bug 220950] + */ + + if (isdigit(UCHAR(Tcl_GetString(objv[2])[0])) + && Tcl_GetIntFromObj(NULL, objv[2], &first) == TCL_OK) { + if (first >= menuPtr->numEntries) { + goto done; + } + } else if (TkGetMenuIndex(interp,menuPtr,objv[2],0,&first) != TCL_OK){ + goto error; + } + if (objc == 3) { + last = first; + } else if (TkGetMenuIndex(interp,menuPtr,objv[3],0,&last) != TCL_OK) { + goto error; + } + + if (menuPtr->tearoff && (first == 0)) { + /* + * Sorry, can't delete the tearoff entry; must reconfigure the + * menu. + */ + + first = 1; + } + if ((first < 0) || (last < first)) { + goto done; + } + DeleteMenuCloneEntries(menuPtr, first, last); + break; + } + case MENU_ENTRYCGET: { + int index; + Tcl_Obj *resultPtr; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "index option"); + goto error; + } + if (TkGetMenuIndex(interp, menuPtr, objv[2], 0, &index) != TCL_OK) { + goto error; + } + if (index < 0) { + goto done; + } + mePtr = menuPtr->entries[index]; + Tcl_Preserve(mePtr); + resultPtr = Tk_GetOptionValue(interp, (char *) mePtr, + mePtr->optionTable, objv[3], menuPtr->tkwin); + Tcl_Release(mePtr); + if (resultPtr == NULL) { + goto error; + } + Tcl_SetObjResult(interp, resultPtr); + break; + } + case MENU_ENTRYCONFIGURE: { + int index; + Tcl_Obj *resultPtr; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index ?-option value ...?"); + goto error; + } + if (TkGetMenuIndex(interp, menuPtr, objv[2], 0, &index) != TCL_OK) { + goto error; + } + if (index < 0) { + goto done; + } + mePtr = menuPtr->entries[index]; + Tcl_Preserve(mePtr); + if (objc == 3) { + resultPtr = Tk_GetOptionInfo(interp, (char *) mePtr, + mePtr->optionTable, NULL, menuPtr->tkwin); + if (resultPtr == NULL) { + result = TCL_ERROR; + } else { + result = TCL_OK; + Tcl_SetObjResult(interp, resultPtr); + } + } else if (objc == 4) { + resultPtr = Tk_GetOptionInfo(interp, (char *) mePtr, + mePtr->optionTable, objv[3], menuPtr->tkwin); + if (resultPtr == NULL) { + result = TCL_ERROR; + } else { + result = TCL_OK; + Tcl_SetObjResult(interp, resultPtr); + } + } else { + result = ConfigureMenuCloneEntries(interp, menuPtr, index, + objc-3, objv+3); + } + Tcl_Release(mePtr); + break; + } + case MENU_INDEX: { + int index; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "string"); + goto error; + } + if (TkGetMenuIndex(interp, menuPtr, objv[2], 0, &index) != TCL_OK) { + goto error; + } + if (index < 0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("none", -1)); + } else { + Tcl_SetObjResult(interp, Tcl_NewIntObj(index)); + } + break; + } + case MENU_INSERT: + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, + "index type ?-option value ...?"); + goto error; + } + if (MenuAddOrInsert(interp,menuPtr,objv[2],objc-3,objv+3) != TCL_OK) { + goto error; + } + break; + case MENU_INVOKE: { + int index; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); + goto error; + } + if (TkGetMenuIndex(interp, menuPtr, objv[2], 0, &index) != TCL_OK) { + goto error; + } + if (index < 0) { + goto done; + } + result = TkInvokeMenu(interp, menuPtr, index); + break; + } + case MENU_POST: { + int x, y; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "x y"); + goto error; + } + if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) { + goto error; + } + + /* + * Tearoff menus are posted differently on Mac and Windows than + * non-tearoffs. TkpPostMenu does not actually map the menu's window + * on those platforms, and popup menus have to be handled specially. + * Also, menubar menues are not intended to be posted (bug 1567681, + * 2160206). + */ + + if (menuPtr->menuType == MENUBAR) { + Tcl_AppendResult(interp, "a menubar menu cannot be posted", NULL); + return TCL_ERROR; + } else if (menuPtr->menuType != TEAROFF_MENU) { + result = TkpPostMenu(interp, menuPtr, x, y); + } else { + result = TkPostTearoffMenu(interp, menuPtr, x, y); + } + break; + } + case MENU_POSTCASCADE: { + int index; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); + goto error; + } + + if (TkGetMenuIndex(interp, menuPtr, objv[2], 0, &index) != TCL_OK) { + goto error; + } + if ((index < 0) || (menuPtr->entries[index]->type != CASCADE_ENTRY)) { + result = TkPostSubmenu(interp, menuPtr, NULL); + } else { + result = TkPostSubmenu(interp, menuPtr, menuPtr->entries[index]); + } + break; + } + case MENU_TYPE: { + int index; + const char *typeStr; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); + goto error; + } + if (TkGetMenuIndex(interp, menuPtr, objv[2], 0, &index) != TCL_OK) { + goto error; + } + if (index < 0) { + goto done; + } + if (menuPtr->entries[index]->type == TEAROFF_ENTRY) { + typeStr = "tearoff"; + } else { + typeStr = menuEntryTypeStrings[menuPtr->entries[index]->type]; + } + Tcl_SetObjResult(interp, Tcl_NewStringObj(typeStr, -1)); + break; + } + case MENU_UNPOST: + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, NULL); + goto error; + } + Tk_UnmapWindow(menuPtr->tkwin); + result = TkPostSubmenu(interp, menuPtr, NULL); + break; + case MENU_XPOSITION: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); + goto error; + } + result = MenuDoXPosition(interp, menuPtr, objv[2]); + break; + case MENU_YPOSITION: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); + goto error; + } + result = MenuDoYPosition(interp, menuPtr, objv[2]); + break; + } + done: + Tcl_Release(menuPtr); + return result; + + error: + Tcl_Release(menuPtr); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * TkInvokeMenu -- + * + * Given a menu and an index, takes the appropriate action for the entry + * associated with that index. + * + * Results: + * Standard Tcl result. + * + * Side effects: + * Commands may get excecuted; variables may get set; sub-menus may get + * posted. + * + *---------------------------------------------------------------------- + */ + +int +TkInvokeMenu( + Tcl_Interp *interp, /* The interp that the menu lives in. */ + TkMenu *menuPtr, /* The menu we are invoking. */ + int index) /* The zero based index of the item we are + * invoking. */ +{ + int result = TCL_OK; + TkMenuEntry *mePtr; + + if (index < 0) { + goto done; + } + mePtr = menuPtr->entries[index]; + if (mePtr->state == ENTRY_DISABLED) { + goto done; + } + + Tcl_Preserve(mePtr); + if (mePtr->type == TEAROFF_ENTRY) { + Tcl_DString ds; + + Tcl_DStringInit(&ds); + Tcl_DStringAppend(&ds, "tk::TearOffMenu ", -1); + Tcl_DStringAppend(&ds, Tk_PathName(menuPtr->tkwin), -1); + result = Tcl_EvalEx(interp, Tcl_DStringValue(&ds), -1, 0); + Tcl_DStringFree(&ds); + } else if ((mePtr->type == CHECK_BUTTON_ENTRY) + && (mePtr->namePtr != NULL)) { + Tcl_Obj *valuePtr; + + if (mePtr->entryFlags & ENTRY_SELECTED) { + valuePtr = mePtr->offValuePtr; + } else { + valuePtr = mePtr->onValuePtr; + } + if (valuePtr == NULL) { + valuePtr = Tcl_NewObj(); + } + Tcl_IncrRefCount(valuePtr); + if (Tcl_ObjSetVar2(interp, mePtr->namePtr, NULL, valuePtr, + TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { + result = TCL_ERROR; + } + Tcl_DecrRefCount(valuePtr); + } else if ((mePtr->type == RADIO_BUTTON_ENTRY) + && (mePtr->namePtr != NULL)) { + Tcl_Obj *valuePtr = mePtr->onValuePtr; + + if (valuePtr == NULL) { + valuePtr = Tcl_NewObj(); + } + Tcl_IncrRefCount(valuePtr); + if (Tcl_ObjSetVar2(interp, mePtr->namePtr, NULL, valuePtr, + TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { + result = TCL_ERROR; + } + Tcl_DecrRefCount(valuePtr); + } + + /* + * We check numEntries in addition to whether the menu entry has a command + * because that goes to zero if the menu gets deleted (e.g., during + * command evaluation). + */ + + if ((menuPtr->numEntries != 0) && (result == TCL_OK) + && (mePtr->commandPtr != NULL)) { + Tcl_Obj *commandPtr = mePtr->commandPtr; + + Tcl_IncrRefCount(commandPtr); + result = Tcl_EvalObjEx(interp, commandPtr, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(commandPtr); + } + Tcl_Release(mePtr); + + done: + return result; +} + +/* + *---------------------------------------------------------------------- + * + * DestroyMenuInstance -- + * + * This function is invoked by TkDestroyMenu to clean up the internal + * structure of a menu at a safe time (when no-one is using it anymore). + * Only takes care of one instance of the menu. + * + * Results: + * None. + * + * Side effects: + * Everything associated with the menu is freed up. + * + *---------------------------------------------------------------------- + */ + +static void +DestroyMenuInstance( + TkMenu *menuPtr) /* Info about menu widget. */ +{ + int i; + TkMenu *menuInstancePtr; + TkMenuEntry *cascadePtr, *nextCascadePtr; + Tcl_Obj *newObjv[2]; + TkMenu *parentMasterMenuPtr; + TkMenuEntry *parentMasterEntryPtr; + ThreadSpecificData *tsdPtr = + Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); + + /* + * If the menu has any cascade menu entries pointing to it, the cascade + * entries need to be told that the menu is going away. We need to clear + * the menu ptr field in the menu reference at this point in the code so + * that everything else can forget about this menu properly. We also need + * to reset -menu field of all entries that are not master menus back to + * this entry name if this is a master menu pointed to by another master + * menu. If there is a clone menu that points to this menu, then this menu + * is itself a clone, so when this menu goes away, the -menu field of the + * pointing entry must be set back to this menu's master menu name so that + * later if another menu is created the cascade hierarchy can be + * maintained. + */ + + TkpDestroyMenu(menuPtr); + if (menuPtr->menuRefPtr == NULL) { + return; + } + cascadePtr = menuPtr->menuRefPtr->parentEntryPtr; + menuPtr->menuRefPtr->menuPtr = NULL; + if (TkFreeMenuReferences(menuPtr->menuRefPtr)) { + menuPtr->menuRefPtr = NULL; + } + + for (; cascadePtr != NULL; cascadePtr = nextCascadePtr) { + nextCascadePtr = cascadePtr->nextCascadePtr; + + if (menuPtr->masterMenuPtr != menuPtr) { + Tcl_Obj *menuNamePtr = Tcl_NewStringObj("-menu", -1); + + parentMasterMenuPtr = cascadePtr->menuPtr->masterMenuPtr; + parentMasterEntryPtr = + parentMasterMenuPtr->entries[cascadePtr->index]; + newObjv[0] = menuNamePtr; + newObjv[1] = parentMasterEntryPtr->namePtr; + + /* + * It is possible that the menu info is out of sync, and these + * things point to NULL, so verify existence [Bug: 3402] + */ + + if (newObjv[0] && newObjv[1]) { + Tcl_IncrRefCount(newObjv[0]); + Tcl_IncrRefCount(newObjv[1]); + ConfigureMenuEntry(cascadePtr, 2, newObjv); + Tcl_DecrRefCount(newObjv[0]); + Tcl_DecrRefCount(newObjv[1]); + } + } else { + ConfigureMenuEntry(cascadePtr, 0, NULL); + } + } + + if (menuPtr->masterMenuPtr != menuPtr) { + for (menuInstancePtr = menuPtr->masterMenuPtr; + menuInstancePtr != NULL; + menuInstancePtr = menuInstancePtr->nextInstancePtr) { + if (menuInstancePtr->nextInstancePtr == menuPtr) { + menuInstancePtr->nextInstancePtr = + menuInstancePtr->nextInstancePtr->nextInstancePtr; + break; + } + } + } else if (menuPtr->nextInstancePtr != NULL) { + Tcl_Panic("Attempting to delete master menu when there are still clones"); + } + + /* + * Free up all the stuff that requires special handling, then let + * Tk_FreeConfigOptions handle all the standard option-related stuff. + */ + + for (i = menuPtr->numEntries; --i >= 0; ) { + /* + * As each menu entry is deleted from the end of the array of entries, + * decrement menuPtr->numEntries. Otherwise, the act of deleting menu + * entry i will dereference freed memory attempting to queue a redraw + * for menu entries (i+1)...numEntries. + */ + + DestroyMenuEntry(menuPtr->entries[i]); + menuPtr->numEntries = i; + } + if (menuPtr->entries != NULL) { + ckfree(menuPtr->entries); + } + TkMenuFreeDrawOptions(menuPtr); + Tk_FreeConfigOptions((char *) menuPtr, + tsdPtr->menuOptionTable, menuPtr->tkwin); + if (menuPtr->tkwin != NULL) { + Tk_Window tkwin = menuPtr->tkwin; + + menuPtr->tkwin = NULL; + Tk_DestroyWindow(tkwin); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkDestroyMenu -- + * + * This function is invoked by Tcl_EventuallyFree or Tcl_Release to clean + * up the internal structure of a menu at a safe time (when no-one is + * using it anymore). If called on a master instance, destroys all of the + * slave instances. If called on a non-master instance, just destroys + * that instance. + * + * Results: + * None. + * + * Side effects: + * Everything associated with the menu is freed up. + * + *---------------------------------------------------------------------- + */ + +void +TkDestroyMenu( + TkMenu *menuPtr) /* Info about menu widget. */ +{ + TkMenu *menuInstancePtr; + TkMenuTopLevelList *topLevelListPtr, *nextTopLevelPtr; + + if (menuPtr->menuFlags & MENU_DELETION_PENDING) { + return; + } + + Tcl_Preserve(menuPtr); + + /* + * Now destroy all non-tearoff instances of this menu if this is a parent + * menu. Is this loop safe enough? Are there going to be destroy bindings + * on child menus which kill the parent? If not, we have to do a slightly + * more complex scheme. + */ + + menuPtr->menuFlags |= MENU_DELETION_PENDING; + if (menuPtr->menuRefPtr != NULL) { + /* + * If any toplevel widgets have this menu as their menubar, the + * geometry of the window may have to be recalculated. + */ + + topLevelListPtr = menuPtr->menuRefPtr->topLevelListPtr; + while (topLevelListPtr != NULL) { + nextTopLevelPtr = topLevelListPtr->nextPtr; + TkpSetWindowMenuBar(topLevelListPtr->tkwin, NULL); + topLevelListPtr = nextTopLevelPtr; + } + } + if (menuPtr->masterMenuPtr == menuPtr) { + while (menuPtr->nextInstancePtr != NULL) { + menuInstancePtr = menuPtr->nextInstancePtr; + menuPtr->nextInstancePtr = menuInstancePtr->nextInstancePtr; + if (menuInstancePtr->tkwin != NULL) { + Tk_Window tkwin = menuInstancePtr->tkwin; + + /* + * Note: it may be desirable to NULL out the tkwin field of + * menuInstancePtr here: + * menuInstancePtr->tkwin = NULL; + */ + + Tk_DestroyWindow(tkwin); + } + } + } + + DestroyMenuInstance(menuPtr); + + Tcl_Release(menuPtr); +} + +/* + *---------------------------------------------------------------------- + * + * UnhookCascadeEntry -- + * + * This entry is removed from the list of entries that point to the + * cascade menu. This is done in preparation for changing the menu that + * this entry points to. + * + * At the end of this function, the menu entry no longer contains a + * reference to a 'TkMenuReferences' structure, and therefore no such + * structure contains a reference to this menu entry either. + * + * Results: + * None + * + * Side effects: + * The appropriate lists are modified. + * + *---------------------------------------------------------------------- + */ + +static void +UnhookCascadeEntry( + TkMenuEntry *mePtr) /* The cascade entry we are removing from the + * cascade list. */ +{ + TkMenuEntry *cascadeEntryPtr; + TkMenuEntry *prevCascadePtr; + TkMenuReferences *menuRefPtr; + + menuRefPtr = mePtr->childMenuRefPtr; + if (menuRefPtr == NULL) { + return; + } + + cascadeEntryPtr = menuRefPtr->parentEntryPtr; + if (cascadeEntryPtr == NULL) { + TkFreeMenuReferences(menuRefPtr); + mePtr->childMenuRefPtr = NULL; + return; + } + + /* + * Singularly linked list deletion. The two special cases are 1. one + * element; 2. The first element is the one we want. + */ + + if (cascadeEntryPtr == mePtr) { + if (cascadeEntryPtr->nextCascadePtr == NULL) { + /* + * This is the last menu entry which points to this menu, so we + * need to clear out the list pointer in the cascade itself. + */ + + menuRefPtr->parentEntryPtr = NULL; + + /* + * The original field is set to zero below, after it is freed. + */ + + TkFreeMenuReferences(menuRefPtr); + } else { + menuRefPtr->parentEntryPtr = cascadeEntryPtr->nextCascadePtr; + } + mePtr->nextCascadePtr = NULL; + } else { + for (prevCascadePtr = cascadeEntryPtr, + cascadeEntryPtr = cascadeEntryPtr->nextCascadePtr; + cascadeEntryPtr != NULL; + prevCascadePtr = cascadeEntryPtr, + cascadeEntryPtr = cascadeEntryPtr->nextCascadePtr) { + if (cascadeEntryPtr == mePtr){ + prevCascadePtr->nextCascadePtr = + cascadeEntryPtr->nextCascadePtr; + cascadeEntryPtr->nextCascadePtr = NULL; + break; + } + } + mePtr->nextCascadePtr = NULL; + } + mePtr->childMenuRefPtr = NULL; +} + +/* + *---------------------------------------------------------------------- + * + * DestroyMenuEntry -- + * + * This function is invoked by Tcl_EventuallyFree or Tcl_Release to clean + * up the internal structure of a menu entry at a safe time (when no-one + * is using it anymore). + * + * Results: + * None. + * + * Side effects: + * Everything associated with the menu entry is freed. + * + *---------------------------------------------------------------------- + */ + +static void +DestroyMenuEntry( + void *memPtr) /* Pointer to entry to be freed. */ +{ + register TkMenuEntry *mePtr = memPtr; + TkMenu *menuPtr = mePtr->menuPtr; + + if (menuPtr->postedCascade == mePtr) { + /* + * Ignore errors while unposting the menu, since it's possible that + * the menu has already been deleted and the unpost will generate an + * error. + */ + + TkPostSubmenu(menuPtr->interp, menuPtr, NULL); + } + + /* + * Free up all the stuff that requires special handling, then let + * Tk_FreeConfigOptions handle all the standard option-related stuff. + */ + + if (mePtr->type == CASCADE_ENTRY) { + if (menuPtr->masterMenuPtr != menuPtr) { + TkMenu *destroyThis = NULL; + TkMenuReferences *menuRefPtr = mePtr->childMenuRefPtr; + + /* + * The menu as a whole is a clone. We must delete the clone of the + * cascaded menu for the particular entry we are destroying. + */ + + if (menuRefPtr != NULL) { + destroyThis = menuRefPtr->menuPtr; + + /* + * But only if it is a clone. What can happen is that we are + * in the middle of deleting a menu and this menu pointer has + * already been reset to point to the original menu. In that + * case we have nothing special to do. + */ + + if ((destroyThis != NULL) + && (destroyThis->masterMenuPtr == destroyThis)) { + destroyThis = NULL; + } + } + UnhookCascadeEntry(mePtr); + menuRefPtr = mePtr->childMenuRefPtr; + if (menuRefPtr != NULL) { + if (menuRefPtr->menuPtr == destroyThis) { + menuRefPtr->menuPtr = NULL; + } + } + if (destroyThis != NULL) { + TkDestroyMenu(destroyThis); + } + } else { + UnhookCascadeEntry(mePtr); + } + } + if (mePtr->image != NULL) { + Tk_FreeImage(mePtr->image); + } + if (mePtr->selectImage != NULL) { + Tk_FreeImage(mePtr->selectImage); + } + if (((mePtr->type == CHECK_BUTTON_ENTRY) + || (mePtr->type == RADIO_BUTTON_ENTRY)) + && (mePtr->namePtr != NULL)) { + const char *varName = Tcl_GetString(mePtr->namePtr); + + Tcl_UntraceVar2(menuPtr->interp, varName, NULL, + TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, + MenuVarProc, mePtr); + } + TkpDestroyMenuEntry(mePtr); + TkMenuEntryFreeDrawOptions(mePtr); + Tk_FreeConfigOptions((char *) mePtr, mePtr->optionTable, menuPtr->tkwin); + ckfree(mePtr); +} + +/* + *--------------------------------------------------------------------------- + * + * MenuWorldChanged -- + * + * This function is called when the world has changed in some way (such + * as the fonts in the system changing) and the widget needs to recompute + * all its graphics contexts and determine its new geometry. + * + * Results: + * None. + * + * Side effects: + * Menu will be relayed out and redisplayed. + * + *--------------------------------------------------------------------------- + */ + +static void +MenuWorldChanged( + ClientData instanceData) /* Information about widget. */ +{ + TkMenu *menuPtr = instanceData; + int i; + + TkMenuConfigureDrawOptions(menuPtr); + for (i = 0; i < menuPtr->numEntries; i++) { + TkMenuConfigureEntryDrawOptions(menuPtr->entries[i], + menuPtr->entries[i]->index); + TkpConfigureMenuEntry(menuPtr->entries[i]); + } + TkEventuallyRecomputeMenu(menuPtr); +} + +/* + *---------------------------------------------------------------------- + * + * ConfigureMenu -- + * + * This function is called to process an argv/argc list, plus the Tk + * option database, in order to configure (or reconfigure) a menu widget. + * + * Results: + * The return value is a standard Tcl result. If TCL_ERROR is returned, + * then the interp's result contains an error message. + * + * Side effects: + * Configuration information, such as colors, font, etc. get set for + * menuPtr; old resources get freed, if there were any. + * + *---------------------------------------------------------------------- + */ + +static int +ConfigureMenu( + Tcl_Interp *interp, /* Used for error reporting. */ + register TkMenu *menuPtr, /* Information about widget; may or may not + * already have values for some fields. */ + int objc, /* Number of valid entries in argv. */ + Tcl_Obj *const objv[]) /* Arguments. */ +{ + int i; + TkMenu *menuListPtr, *cleanupPtr; + int result; + ThreadSpecificData *tsdPtr = + Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); + + for (menuListPtr = menuPtr->masterMenuPtr; menuListPtr != NULL; + menuListPtr = menuListPtr->nextInstancePtr) { + menuListPtr->errorStructPtr = ckalloc(sizeof(Tk_SavedOptions)); + result = Tk_SetOptions(interp, (char *) menuListPtr, + tsdPtr->menuOptionTable, objc, objv, + menuListPtr->tkwin, menuListPtr->errorStructPtr, NULL); + if (result != TCL_OK) { + for (cleanupPtr = menuPtr->masterMenuPtr; + cleanupPtr != menuListPtr; + cleanupPtr = cleanupPtr->nextInstancePtr) { + Tk_RestoreSavedOptions(cleanupPtr->errorStructPtr); + ckfree(cleanupPtr->errorStructPtr); + cleanupPtr->errorStructPtr = NULL; + } + if (menuListPtr->errorStructPtr != NULL) { + Tk_RestoreSavedOptions(menuListPtr->errorStructPtr); + ckfree(menuListPtr->errorStructPtr); + menuListPtr->errorStructPtr = NULL; + } + return TCL_ERROR; + } + + /* + * When a menu is created, the type is in all of the arguments to the + * menu command. Let Tk_ConfigureWidget take care of parsing them, and + * then set the type after we can look at the type string. Once set, a + * menu's type cannot be changed + */ + + if (menuListPtr->menuType == UNKNOWN_TYPE) { + Tcl_GetIndexFromObjStruct(NULL, menuListPtr->menuTypePtr, + menuTypeStrings, sizeof(char *), NULL, 0, &menuListPtr->menuType); + + /* + * Configure the new window to be either a pop-up menu or a + * tear-off menu. We don't do this for menubars since they are not + * toplevel windows. Also, since this gets called before CloneMenu + * has a chance to set the menuType field, we have to look at the + * menuTypeName field to tell that this is a menu bar. + */ + + if (menuListPtr->menuType == MASTER_MENU) { + int typeFlag = TK_MAKE_MENU_POPUP; + Tk_Window tkwin = menuPtr->tkwin; + + /* + * Work out if we are the child of a menubar or a popup. + */ + + while (1) { + Tk_Window parent = Tk_Parent(tkwin); + + if (Tk_Class(parent) != Tk_Class(menuPtr->tkwin)) { + break; + } + tkwin = parent; + } + if (((TkMenu *) tkwin)->menuType == MENUBAR) { + typeFlag = TK_MAKE_MENU_DROPDOWN; + } + + TkpMakeMenuWindow(menuListPtr->tkwin, typeFlag); + } else if (menuListPtr->menuType == TEAROFF_MENU) { + TkpMakeMenuWindow(menuListPtr->tkwin, TK_MAKE_MENU_TEAROFF); + } + } + + /* + * Depending on the -tearOff option, make sure that there is or isn't + * an initial tear-off entry at the beginning of the menu. + */ + + if (menuListPtr->tearoff) { + if ((menuListPtr->numEntries == 0) + || (menuListPtr->entries[0]->type != TEAROFF_ENTRY)) { + if (MenuNewEntry(menuListPtr, 0, TEAROFF_ENTRY) == NULL) { + for (cleanupPtr = menuPtr->masterMenuPtr; + cleanupPtr != menuListPtr; + cleanupPtr = cleanupPtr->nextInstancePtr) { + Tk_RestoreSavedOptions(cleanupPtr->errorStructPtr); + ckfree(cleanupPtr->errorStructPtr); + cleanupPtr->errorStructPtr = NULL; + } + if (menuListPtr->errorStructPtr != NULL) { + Tk_RestoreSavedOptions(menuListPtr->errorStructPtr); + ckfree(menuListPtr->errorStructPtr); + menuListPtr->errorStructPtr = NULL; + } + return TCL_ERROR; + } + } + } else if ((menuListPtr->numEntries > 0) + && (menuListPtr->entries[0]->type == TEAROFF_ENTRY)) { + int i; + + Tcl_EventuallyFree(menuListPtr->entries[0], (Tcl_FreeProc *) DestroyMenuEntry); + + for (i = 0; i < menuListPtr->numEntries - 1; i++) { + menuListPtr->entries[i] = menuListPtr->entries[i + 1]; + menuListPtr->entries[i]->index = i; + } + menuListPtr->numEntries--; + if (menuListPtr->numEntries == 0) { + ckfree(menuListPtr->entries); + menuListPtr->entries = NULL; + } + } + + TkMenuConfigureDrawOptions(menuListPtr); + + /* + * After reconfiguring a menu, we need to reconfigure all of the + * entries in the menu, since some of the things in the children (such + * as graphics contexts) may have to change to reflect changes in the + * parent. + */ + + for (i = 0; i < menuListPtr->numEntries; i++) { + TkMenuEntry *mePtr; + + mePtr = menuListPtr->entries[i]; + ConfigureMenuEntry(mePtr, 0, NULL); + } + + TkEventuallyRecomputeMenu(menuListPtr); + } + + for (cleanupPtr = menuPtr->masterMenuPtr; cleanupPtr != NULL; + cleanupPtr = cleanupPtr->nextInstancePtr) { + Tk_FreeSavedOptions(cleanupPtr->errorStructPtr); + ckfree(cleanupPtr->errorStructPtr); + cleanupPtr->errorStructPtr = NULL; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * PostProcessEntry -- + * + * This is called by ConfigureMenuEntry to do all of the configuration + * after Tk_SetOptions is called. This is separate so that error handling + * is easier. + * + * Results: + * The return value is a standard Tcl result. If TCL_ERROR is returned, + * then the interp's result contains an error message. + * + * Side effects: + * Configuration information such as label and accelerator get set for + * mePtr; old resources get freed, if there were any. + * + *---------------------------------------------------------------------- + */ + +static int +PostProcessEntry( + TkMenuEntry *mePtr) /* The entry we are configuring. */ +{ + TkMenu *menuPtr = mePtr->menuPtr; + int index = mePtr->index; + const char *name; + Tk_Image image; + + /* + * The code below handles special configuration stuff not taken care of by + * Tk_ConfigureWidget, such as special processing for defaults, sizing + * strings, graphics contexts, etc. + */ + + if (mePtr->labelPtr == NULL) { + mePtr->labelLength = 0; + } else { + (void)Tcl_GetStringFromObj(mePtr->labelPtr, &mePtr->labelLength); + } + if (mePtr->accelPtr == NULL) { + mePtr->accelLength = 0; + } else { + (void)Tcl_GetStringFromObj(mePtr->accelPtr, &mePtr->accelLength); + } + + /* + * If this is a cascade entry, the platform-specific data of the child + * menu has to be updated. Also, the links that point to parents and + * cascades have to be updated. + */ + + if ((mePtr->type == CASCADE_ENTRY) && (mePtr->namePtr != NULL)) { + TkMenuEntry *cascadeEntryPtr; + int alreadyThere; + TkMenuReferences *menuRefPtr; + char *oldHashKey = NULL; /* Initialization only needed to + * prevent compiler warning. */ + + /* + * This is a cascade entry. If the menu that the cascade entry is + * pointing to has changed, we need to remove this entry from the list + * of entries pointing to the old menu, and add a cascade reference to + * the list of entries pointing to the new menu. + * + * BUG: We are not recloning for special case #3 yet. + */ + + name = Tcl_GetString(mePtr->namePtr); + if (mePtr->childMenuRefPtr != NULL) { + oldHashKey = Tcl_GetHashKey(TkGetMenuHashTable(menuPtr->interp), + mePtr->childMenuRefPtr->hashEntryPtr); + if (strcmp(oldHashKey, name) != 0) { + UnhookCascadeEntry(mePtr); + } + } + + if ((mePtr->childMenuRefPtr == NULL) + || (strcmp(oldHashKey, name) != 0)) { + menuRefPtr = TkCreateMenuReferences(menuPtr->interp, name); + mePtr->childMenuRefPtr = menuRefPtr; + + if (menuRefPtr->parentEntryPtr == NULL) { + menuRefPtr->parentEntryPtr = mePtr; + } else { + alreadyThere = 0; + for (cascadeEntryPtr = menuRefPtr->parentEntryPtr; + cascadeEntryPtr != NULL; + cascadeEntryPtr = + cascadeEntryPtr->nextCascadePtr) { + if (cascadeEntryPtr == mePtr) { + alreadyThere = 1; + break; + } + } + + /* + * Put the item at the front of the list. + */ + + if (!alreadyThere) { + mePtr->nextCascadePtr = menuRefPtr->parentEntryPtr; + menuRefPtr->parentEntryPtr = mePtr; + } + } + } + } + + if (TkMenuConfigureEntryDrawOptions(mePtr, index) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Get the images for the entry, if there are any. Allocate the new images + * before freeing the old ones, so that the reference counts don't go to + * zero and cause image data to be discarded. + */ + + if (mePtr->imagePtr != NULL) { + const char *imageString = Tcl_GetString(mePtr->imagePtr); + + image = Tk_GetImage(menuPtr->interp, menuPtr->tkwin, imageString, + TkMenuImageProc, mePtr); + if (image == NULL) { + return TCL_ERROR; + } + } else { + image = NULL; + } + if (mePtr->image != NULL) { + Tk_FreeImage(mePtr->image); + } + mePtr->image = image; + if (mePtr->selectImagePtr != NULL) { + const char *selectImageString = Tcl_GetString(mePtr->selectImagePtr); + + image = Tk_GetImage(menuPtr->interp, menuPtr->tkwin, selectImageString, + TkMenuSelectImageProc, mePtr); + if (image == NULL) { + return TCL_ERROR; + } + } else { + image = NULL; + } + if (mePtr->selectImage != NULL) { + Tk_FreeImage(mePtr->selectImage); + } + mePtr->selectImage = image; + + if ((mePtr->type == CHECK_BUTTON_ENTRY) + || (mePtr->type == RADIO_BUTTON_ENTRY)) { + Tcl_Obj *valuePtr; + const char *name; + + if (mePtr->namePtr == NULL) { + if (mePtr->labelPtr == NULL) { + mePtr->namePtr = NULL; + } else { + mePtr->namePtr = Tcl_DuplicateObj(mePtr->labelPtr); + Tcl_IncrRefCount(mePtr->namePtr); + } + } + if (mePtr->onValuePtr == NULL) { + if (mePtr->labelPtr == NULL) { + mePtr->onValuePtr = NULL; + } else { + mePtr->onValuePtr = Tcl_DuplicateObj(mePtr->labelPtr); + Tcl_IncrRefCount(mePtr->onValuePtr); + } + } + + /* + * Select the entry if the associated variable has the appropriate + * value, initialize the variable if it doesn't exist, then set a + * trace on the variable to monitor future changes to its value. + */ + + if (mePtr->namePtr != NULL) { + valuePtr = Tcl_ObjGetVar2(menuPtr->interp, mePtr->namePtr, NULL, + TCL_GLOBAL_ONLY); + } else { + valuePtr = NULL; + } + mePtr->entryFlags &= ~ENTRY_SELECTED; + if (valuePtr != NULL) { + if (mePtr->onValuePtr != NULL) { + const char *value = Tcl_GetString(valuePtr); + const char *onValue = Tcl_GetString(mePtr->onValuePtr); + + if (strcmp(value, onValue) == 0) { + mePtr->entryFlags |= ENTRY_SELECTED; + } + } + } else { + if (mePtr->namePtr != NULL) { + Tcl_ObjSetVar2(menuPtr->interp, mePtr->namePtr, NULL, + (mePtr->type == CHECK_BUTTON_ENTRY) + ? mePtr->offValuePtr : Tcl_NewObj(), TCL_GLOBAL_ONLY); + } + } + if (mePtr->namePtr != NULL) { + name = Tcl_GetString(mePtr->namePtr); + Tcl_TraceVar2(menuPtr->interp, name, + NULL, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, + MenuVarProc, mePtr); + } + } + + if (TkpConfigureMenuEntry(mePtr) != TCL_OK) { + return TCL_ERROR; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ConfigureMenuEntry -- + * + * This function is called to process an argv/argc list in order to + * configure (or reconfigure) one entry in a menu. + * + * Results: + * The return value is a standard Tcl result. If TCL_ERROR is returned, + * then the interp's result contains an error message. + * + * Side effects: + * Configuration information such as label and accelerator get set for + * mePtr; old resources get freed, if there were any. + * + *---------------------------------------------------------------------- + */ + +static int +ConfigureMenuEntry( + register TkMenuEntry *mePtr,/* Information about menu entry; may or may + * not already have values for some fields. */ + int objc, /* Number of valid entries in argv. */ + Tcl_Obj *const objv[]) /* Arguments. */ +{ + TkMenu *menuPtr = mePtr->menuPtr; + Tk_SavedOptions errorStruct; + int result; + + /* + * If this entry is a check button or radio button, then remove its old + * trace function. + */ + + if ((mePtr->namePtr != NULL) + && ((mePtr->type == CHECK_BUTTON_ENTRY) + || (mePtr->type == RADIO_BUTTON_ENTRY))) { + const char *name = Tcl_GetString(mePtr->namePtr); + + Tcl_UntraceVar2(menuPtr->interp, name, NULL, + TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, + MenuVarProc, mePtr); + } + + result = TCL_OK; + if (menuPtr->tkwin != NULL) { + if (Tk_SetOptions(menuPtr->interp, (char *) mePtr, + mePtr->optionTable, objc, objv, menuPtr->tkwin, + &errorStruct, NULL) != TCL_OK) { + return TCL_ERROR; + } + result = PostProcessEntry(mePtr); + if (result != TCL_OK) { + Tk_RestoreSavedOptions(&errorStruct); + PostProcessEntry(mePtr); + } + Tk_FreeSavedOptions(&errorStruct); + } + + TkEventuallyRecomputeMenu(menuPtr); + + return result; +} + +/* + *---------------------------------------------------------------------- + * + * ConfigureMenuCloneEntries -- + * + * Calls ConfigureMenuEntry for each menu in the clone chain. + * + * Results: + * The return value is a standard Tcl result. If TCL_ERROR is returned, + * then the interp's result contains an error message. + * + * Side effects: + * Configuration information such as label and accelerator get set for + * mePtr; old resources get freed, if there were any. + * + *---------------------------------------------------------------------- + */ + +static int +ConfigureMenuCloneEntries( + Tcl_Interp *interp, /* Used for error reporting. */ + TkMenu *menuPtr, /* Information about whole menu. */ + int index, /* Index of mePtr within menuPtr's entries. */ + int objc, /* Number of valid entries in argv. */ + Tcl_Obj *const objv[]) /* Arguments. */ +{ + TkMenuEntry *mePtr; + TkMenu *menuListPtr; + int cascadeEntryChanged = 0; + TkMenuReferences *oldCascadeMenuRefPtr, *cascadeMenuRefPtr = NULL; + Tcl_Obj *oldCascadePtr = NULL; + const char *newCascadeName; + + /* + * Cascades are kind of tricky here. This is special case #3 in the + * comment at the top of this file. Basically, if a menu is the master + * menu of a clone chain, and has an entry with a cascade menu, the clones + * of the menu will point to clones of the cascade menu. We have to + * destroy the clones of the cascades, clone the new cascade menu, and + * configure the entry to point to the new clone. + */ + + mePtr = menuPtr->masterMenuPtr->entries[index]; + if (mePtr->type == CASCADE_ENTRY) { + oldCascadePtr = mePtr->namePtr; + if (oldCascadePtr != NULL) { + Tcl_IncrRefCount(oldCascadePtr); + } + } + + if (ConfigureMenuEntry(mePtr, objc, objv) != TCL_OK) { + return TCL_ERROR; + } + + if (mePtr->type == CASCADE_ENTRY) { + const char *oldCascadeName; + + if (mePtr->namePtr != NULL) { + newCascadeName = Tcl_GetString(mePtr->namePtr); + } else { + newCascadeName = NULL; + } + + if ((oldCascadePtr == NULL) && (mePtr->namePtr == NULL)) { + cascadeEntryChanged = 0; + } else if (((oldCascadePtr == NULL) && (mePtr->namePtr != NULL)) + || ((oldCascadePtr != NULL) + && (mePtr->namePtr == NULL))) { + cascadeEntryChanged = 1; + } else { + oldCascadeName = Tcl_GetString(oldCascadePtr); + cascadeEntryChanged = (strcmp(oldCascadeName, newCascadeName) + != 0); + } + if (oldCascadePtr != NULL) { + Tcl_DecrRefCount(oldCascadePtr); + } + } + + if (cascadeEntryChanged) { + if (mePtr->namePtr != NULL) { + newCascadeName = Tcl_GetString(mePtr->namePtr); + cascadeMenuRefPtr = TkFindMenuReferences(menuPtr->interp, + newCascadeName); + } + } + + for (menuListPtr = menuPtr->masterMenuPtr->nextInstancePtr; + menuListPtr != NULL; + menuListPtr = menuListPtr->nextInstancePtr) { + + mePtr = menuListPtr->entries[index]; + + if (cascadeEntryChanged && (mePtr->namePtr != NULL)) { + oldCascadeMenuRefPtr = TkFindMenuReferencesObj(menuPtr->interp, + mePtr->namePtr); + + if ((oldCascadeMenuRefPtr != NULL) + && (oldCascadeMenuRefPtr->menuPtr != NULL)) { + RecursivelyDeleteMenu(oldCascadeMenuRefPtr->menuPtr); + } + } + + if (ConfigureMenuEntry(mePtr, objc, objv) != TCL_OK) { + return TCL_ERROR; + } + + if (cascadeEntryChanged && (mePtr->namePtr != NULL)) { + if (cascadeMenuRefPtr && cascadeMenuRefPtr->menuPtr != NULL) { + Tcl_Obj *newObjv[2]; + Tcl_Obj *newCloneNamePtr; + Tcl_Obj *pathNamePtr = Tcl_NewStringObj( + Tk_PathName(menuListPtr->tkwin), -1); + Tcl_Obj *normalPtr = Tcl_NewStringObj("normal", -1); + Tcl_Obj *menuObjPtr = Tcl_NewStringObj("-menu", -1); + + Tcl_IncrRefCount(pathNamePtr); + newCloneNamePtr = TkNewMenuName(menuPtr->interp, + pathNamePtr, + cascadeMenuRefPtr->menuPtr); + Tcl_IncrRefCount(newCloneNamePtr); + Tcl_IncrRefCount(normalPtr); + CloneMenu(cascadeMenuRefPtr->menuPtr, newCloneNamePtr, + normalPtr); + + newObjv[0] = menuObjPtr; + newObjv[1] = newCloneNamePtr; + Tcl_IncrRefCount(menuObjPtr); + ConfigureMenuEntry(mePtr, 2, newObjv); + Tcl_DecrRefCount(newCloneNamePtr); + Tcl_DecrRefCount(pathNamePtr); + Tcl_DecrRefCount(normalPtr); + Tcl_DecrRefCount(menuObjPtr); + } + } + } + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * TkGetMenuIndex -- + * + * Parse a textual index into a menu and return the numerical index of + * the indicated entry. + * + * Results: + * A standard Tcl result. If all went well, then *indexPtr is filled in + * with the entry index corresponding to string (ranges from -1 to the + * number of entries in the menu minus one). Otherwise an error message + * is left in the interp's result. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +int +TkGetMenuIndex( + Tcl_Interp *interp, /* For error messages. */ + TkMenu *menuPtr, /* Menu for which the index is being + * specified. */ + Tcl_Obj *objPtr, /* Specification of an entry in menu. See + * manual entry for valid .*/ + int lastOK, /* Non-zero means its OK to return index just + * *after* last entry. */ + int *indexPtr) /* Where to store converted index. */ +{ + int i; + const char *string = Tcl_GetString(objPtr); + + if ((string[0] == 'a') && (strcmp(string, "active") == 0)) { + *indexPtr = menuPtr->active; + goto success; + } + + if (((string[0] == 'l') && (strcmp(string, "last") == 0)) + || ((string[0] == 'e') && (strcmp(string, "end") == 0))) { + *indexPtr = menuPtr->numEntries - ((lastOK) ? 0 : 1); + goto success; + } + + if ((string[0] == 'n') && (strcmp(string, "none") == 0)) { + *indexPtr = -1; + goto success; + } + + if (string[0] == '@') { + if (GetIndexFromCoords(interp, menuPtr, string, indexPtr) + == TCL_OK) { + goto success; + } + } + + if (isdigit(UCHAR(string[0]))) { + if (Tcl_GetInt(interp, string, &i) == TCL_OK) { + if (i >= menuPtr->numEntries) { + if (lastOK) { + i = menuPtr->numEntries; + } else { + i = menuPtr->numEntries-1; + } + } else if (i < 0) { + i = -1; + } + *indexPtr = i; + goto success; + } + Tcl_ResetResult(interp); + } + + for (i = 0; i < menuPtr->numEntries; i++) { + Tcl_Obj *labelPtr = menuPtr->entries[i]->labelPtr; + const char *label = (labelPtr == NULL) ? NULL : Tcl_GetString(labelPtr); + + if ((label != NULL) && (Tcl_StringMatch(label, string))) { + *indexPtr = i; + goto success; + } + } + + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad menu entry index \"%s\"", string)); + Tcl_SetErrorCode(interp, "TK", "MENU", "INDEX", NULL); + return TCL_ERROR; + + success: + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * MenuCmdDeletedProc -- + * + * This function is invoked when a widget command is deleted. If the + * widget isn't already in the process of being destroyed, this command + * destroys it. + * + * Results: + * None. + * + * Side effects: + * The widget is destroyed. + * + *---------------------------------------------------------------------- + */ + +static void +MenuCmdDeletedProc( + ClientData clientData) /* Pointer to widget record for widget. */ +{ + TkMenu *menuPtr = clientData; + Tk_Window tkwin = menuPtr->tkwin; + + /* + * This function could be invoked either because the window was destroyed + * and the command was then deleted (in which case tkwin is NULL) or + * because the command was deleted, and then this function destroys the + * widget. + */ + + if (tkwin != NULL) { + /* + * Note: it may be desirable to NULL out the tkwin field of menuPtr + * here: + * menuPtr->tkwin = NULL; + */ + + Tk_DestroyWindow(tkwin); + } +} + +/* + *---------------------------------------------------------------------- + * + * MenuNewEntry -- + * + * This function allocates and initializes a new menu entry. + * + * Results: + * The return value is a pointer to a new menu entry structure, which has + * been malloc-ed, initialized, and entered into the entry array for the + * menu. + * + * Side effects: + * Storage gets allocated. + * + *---------------------------------------------------------------------- + */ + +static TkMenuEntry * +MenuNewEntry( + TkMenu *menuPtr, /* Menu that will hold the new entry. */ + int index, /* Where in the menu the new entry is to + * go. */ + int type) /* The type of the new entry. */ +{ + TkMenuEntry *mePtr; + TkMenuEntry **newEntries; + int i; + ThreadSpecificData *tsdPtr = + Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); + + /* + * Create a new array of entries with an empty slot for the new entry. + */ + + newEntries = ckalloc((menuPtr->numEntries+1) * sizeof(TkMenuEntry *)); + for (i = 0; i < index; i++) { + newEntries[i] = menuPtr->entries[i]; + } + for (; i < menuPtr->numEntries; i++) { + newEntries[i+1] = menuPtr->entries[i]; + newEntries[i+1]->index = i + 1; + } + if (menuPtr->numEntries != 0) { + ckfree(menuPtr->entries); + } + menuPtr->entries = newEntries; + menuPtr->numEntries++; + mePtr = ckalloc(sizeof(TkMenuEntry)); + menuPtr->entries[index] = mePtr; + mePtr->type = type; + mePtr->optionTable = tsdPtr->entryOptionTables[type]; + mePtr->menuPtr = menuPtr; + mePtr->labelPtr = NULL; + mePtr->labelLength = 0; + mePtr->underline = -1; + mePtr->bitmapPtr = NULL; + mePtr->imagePtr = NULL; + mePtr->image = NULL; + mePtr->selectImagePtr = NULL; + mePtr->selectImage = NULL; + mePtr->accelPtr = NULL; + mePtr->accelLength = 0; + mePtr->state = ENTRY_DISABLED; + mePtr->borderPtr = NULL; + mePtr->fgPtr = NULL; + mePtr->activeBorderPtr = NULL; + mePtr->activeFgPtr = NULL; + mePtr->fontPtr = NULL; + mePtr->indicatorOn = 0; + mePtr->indicatorFgPtr = NULL; + mePtr->columnBreak = 0; + mePtr->hideMargin = 0; + mePtr->commandPtr = NULL; + mePtr->namePtr = NULL; + mePtr->childMenuRefPtr = NULL; + mePtr->onValuePtr = NULL; + mePtr->offValuePtr = NULL; + mePtr->entryFlags = 0; + mePtr->index = index; + mePtr->nextCascadePtr = NULL; + if (Tk_InitOptions(menuPtr->interp, (char *) mePtr, + mePtr->optionTable, menuPtr->tkwin) != TCL_OK) { + ckfree(mePtr); + return NULL; + } + TkMenuInitializeEntryDrawingFields(mePtr); + if (TkpMenuNewEntry(mePtr) != TCL_OK) { + Tk_FreeConfigOptions((char *) mePtr, mePtr->optionTable, + menuPtr->tkwin); + ckfree(mePtr); + return NULL; + } + + return mePtr; +} + +/* + *---------------------------------------------------------------------- + * + * MenuAddOrInsert -- + * + * This function does all of the work of the "add" and "insert" widget + * commands, allowing the code for these to be shared. + * + * Results: + * A standard Tcl return value. + * + * Side effects: + * A new menu entry is created in menuPtr. + * + *---------------------------------------------------------------------- + */ + +static int +MenuAddOrInsert( + Tcl_Interp *interp, /* Used for error reporting. */ + TkMenu *menuPtr, /* Widget in which to create new entry. */ + Tcl_Obj *indexPtr, /* Object describing index at which to insert. + * NULL means insert at end. */ + int objc, /* Number of elements in objv. */ + Tcl_Obj *const objv[]) /* Arguments to command: first arg is type of + * entry, others are config options. */ +{ + int type, index; + TkMenuEntry *mePtr; + TkMenu *menuListPtr; + + if (indexPtr != NULL) { + if (TkGetMenuIndex(interp, menuPtr, indexPtr, 1, &index) != TCL_OK) { + return TCL_ERROR; + } + } else { + index = menuPtr->numEntries; + } + if (index < 0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad index \"%s\"", Tcl_GetString(indexPtr))); + Tcl_SetErrorCode(interp, "TK", "MENU", "INDEX", NULL); + return TCL_ERROR; + } + if (menuPtr->tearoff && (index == 0)) { + index = 1; + } + + /* + * Figure out the type of the new entry. + */ + + if (Tcl_GetIndexFromObjStruct(interp, objv[0], menuEntryTypeStrings, + sizeof(char *), "menu entry type", 0, &type) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Now we have to add an entry for every instance related to this menu. + */ + + for (menuListPtr = menuPtr->masterMenuPtr; menuListPtr != NULL; + menuListPtr = menuListPtr->nextInstancePtr) { + + mePtr = MenuNewEntry(menuListPtr, index, type); + if (mePtr == NULL) { + return TCL_ERROR; + } + if (ConfigureMenuEntry(mePtr, objc - 1, objv + 1) != TCL_OK) { + TkMenu *errorMenuPtr; + int i; + + for (errorMenuPtr = menuPtr->masterMenuPtr; + errorMenuPtr != NULL; + errorMenuPtr = errorMenuPtr->nextInstancePtr) { + Tcl_EventuallyFree(errorMenuPtr->entries[index], + (Tcl_FreeProc *) DestroyMenuEntry); + for (i = index; i < errorMenuPtr->numEntries - 1; i++) { + errorMenuPtr->entries[i] = errorMenuPtr->entries[i + 1]; + errorMenuPtr->entries[i]->index = i; + } + errorMenuPtr->numEntries--; + if (errorMenuPtr->numEntries == 0) { + ckfree(errorMenuPtr->entries); + errorMenuPtr->entries = NULL; + } + if (errorMenuPtr == menuListPtr) { + break; + } + } + return TCL_ERROR; + } + + /* + * If a menu has cascades, then every instance of the menu has to have + * its own parallel cascade structure. So adding an entry to a menu + * with clones means that the menu that the entry points to has to be + * cloned for every clone the master menu has. This is special case #2 + * in the comment at the top of this file. + */ + + if ((menuPtr != menuListPtr) && (type == CASCADE_ENTRY)) { + if ((mePtr->namePtr != NULL) + && (mePtr->childMenuRefPtr != NULL) + && (mePtr->childMenuRefPtr->menuPtr != NULL)) { + TkMenu *cascadeMenuPtr = + mePtr->childMenuRefPtr->menuPtr->masterMenuPtr; + Tcl_Obj *newCascadePtr, *newObjv[2]; + Tcl_Obj *menuNamePtr = Tcl_NewStringObj("-menu", -1); + Tcl_Obj *windowNamePtr = + Tcl_NewStringObj(Tk_PathName(menuListPtr->tkwin), -1); + Tcl_Obj *normalPtr = Tcl_NewStringObj("normal", -1); + TkMenuReferences *menuRefPtr; + + Tcl_IncrRefCount(windowNamePtr); + newCascadePtr = TkNewMenuName(menuListPtr->interp, + windowNamePtr, cascadeMenuPtr); + Tcl_IncrRefCount(newCascadePtr); + Tcl_IncrRefCount(normalPtr); + CloneMenu(cascadeMenuPtr, newCascadePtr, normalPtr); + + menuRefPtr = TkFindMenuReferencesObj(menuListPtr->interp, + newCascadePtr); + if (menuRefPtr == NULL) { + Tcl_Panic("CloneMenu failed inside of MenuAddOrInsert"); + } + newObjv[0] = menuNamePtr; + newObjv[1] = newCascadePtr; + Tcl_IncrRefCount(menuNamePtr); + Tcl_IncrRefCount(newCascadePtr); + ConfigureMenuEntry(mePtr, 2, newObjv); + Tcl_DecrRefCount(newCascadePtr); + Tcl_DecrRefCount(menuNamePtr); + Tcl_DecrRefCount(windowNamePtr); + Tcl_DecrRefCount(normalPtr); + } + } + } + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * MenuVarProc -- + * + * This function is invoked when someone changes the state variable + * associated with a radiobutton or checkbutton menu entry. The entry's + * selected state is set to match the value of the variable. + * + * Results: + * NULL is always returned. + * + * Side effects: + * The menu entry may become selected or deselected. + * + *-------------------------------------------------------------- + */ + +static char * +MenuVarProc( + ClientData clientData, /* Information about menu entry. */ + Tcl_Interp *interp, /* Interpreter containing variable. */ + const char *name1, /* First part of variable's name. */ + const char *name2, /* Second part of variable's name. */ + int flags) /* Describes what just happened. */ +{ + TkMenuEntry *mePtr = clientData; + TkMenu *menuPtr; + const char *value; + const char *name, *onValue; + + if (flags & TCL_INTERP_DESTROYED) { + /* + * Do nothing if the interpreter is going away. + */ + + return NULL; + } + + menuPtr = mePtr->menuPtr; + name = Tcl_GetString(mePtr->namePtr); + + /* + * If the variable is being unset, then re-establish the trace. + */ + + if (flags & TCL_TRACE_UNSETS) { + mePtr->entryFlags &= ~ENTRY_SELECTED; + if (flags & TCL_TRACE_DESTROYED) { + Tcl_TraceVar2(interp, name, NULL, + TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, + MenuVarProc, clientData); + } + TkpConfigureMenuEntry(mePtr); + TkEventuallyRedrawMenu(menuPtr, NULL); + return NULL; + } + + /* + * Use the value of the variable to update the selected status of the menu + * entry. + */ + + value = Tcl_GetVar2(interp, name, NULL, TCL_GLOBAL_ONLY); + if (value == NULL) { + value = ""; + } + if (mePtr->onValuePtr != NULL) { + onValue = Tcl_GetString(mePtr->onValuePtr); + if (strcmp(value, onValue) == 0) { + if (mePtr->entryFlags & ENTRY_SELECTED) { + return NULL; + } + mePtr->entryFlags |= ENTRY_SELECTED; + } else if (mePtr->entryFlags & ENTRY_SELECTED) { + mePtr->entryFlags &= ~ENTRY_SELECTED; + } else { + return NULL; + } + } else { + return NULL; + } + TkpConfigureMenuEntry(mePtr); + TkEventuallyRedrawMenu(menuPtr, mePtr); + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * TkActivateMenuEntry -- + * + * This function is invoked to make a particular menu entry the active + * one, deactivating any other entry that might currently be active. + * + * Results: + * The return value is a standard Tcl result (errors can occur while + * posting and unposting submenus). + * + * Side effects: + * Menu entries get redisplayed, and the active entry changes. Submenus + * may get posted and unposted. + * + *---------------------------------------------------------------------- + */ + +int +TkActivateMenuEntry( + register TkMenu *menuPtr, /* Menu in which to activate. */ + int index) /* Index of entry to activate, or -1 to + * deactivate all entries. */ +{ + register TkMenuEntry *mePtr; + int result = TCL_OK; + + if (menuPtr->active >= 0) { + mePtr = menuPtr->entries[menuPtr->active]; + + /* + * Don't change the state unless it's currently active (state might + * already have been changed to disabled). + */ + + if (mePtr->state == ENTRY_ACTIVE) { + mePtr->state = ENTRY_NORMAL; + } + TkEventuallyRedrawMenu(menuPtr, menuPtr->entries[menuPtr->active]); + } + menuPtr->active = index; + if (index >= 0) { + mePtr = menuPtr->entries[index]; + mePtr->state = ENTRY_ACTIVE; + TkEventuallyRedrawMenu(menuPtr, mePtr); + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TkPostCommand -- + * + * Execute the postcommand for the given menu. + * + * Results: + * The return value is a standard Tcl result (errors can occur while the + * postcommands are being processed). + * + * Side effects: + * Since commands can get executed while this routine is being executed, + * the entire world can change. + * + *---------------------------------------------------------------------- + */ + +int +TkPostCommand( + TkMenu *menuPtr) +{ + int result; + + /* + * If there is a command for the menu, execute it. This may change the + * size of the menu, so be sure to recompute the menu's geometry if + * needed. + */ + + if (menuPtr->postCommandPtr != NULL) { + Tcl_Obj *postCommandPtr = menuPtr->postCommandPtr; + + Tcl_IncrRefCount(postCommandPtr); + result = Tcl_EvalObjEx(menuPtr->interp, postCommandPtr, + TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(postCommandPtr); + if (result != TCL_OK) { + return result; + } + TkRecomputeMenu(menuPtr); + } + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * CloneMenu -- + * + * Creates a child copy of the menu. It will be inserted into the menu's + * instance chain. All attributes and entry attributes will be + * duplicated. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Allocates storage. After the menu is created, any configuration done + * with this menu or any related one will be reflected in all of them. + * + *-------------------------------------------------------------- + */ + +static int +CloneMenu( + TkMenu *menuPtr, /* The menu we are going to clone. */ + Tcl_Obj *newMenuNamePtr, /* The name to give the new menu. */ + Tcl_Obj *newMenuTypePtr) /* What kind of menu is this, a normal menu a + * menubar, or a tearoff? */ +{ + int returnResult; + int menuType, i; + TkMenuReferences *menuRefPtr; + Tcl_Obj *menuDupCommandArray[4]; + + if (newMenuTypePtr == NULL) { + menuType = MASTER_MENU; + } else { + if (Tcl_GetIndexFromObjStruct(menuPtr->interp, newMenuTypePtr, + menuTypeStrings, sizeof(char *), "menu type", 0, &menuType) != TCL_OK) { + return TCL_ERROR; + } + } + + menuDupCommandArray[0] = Tcl_NewStringObj("tk::MenuDup", -1); + menuDupCommandArray[1] = Tcl_NewStringObj(Tk_PathName(menuPtr->tkwin), -1); + menuDupCommandArray[2] = newMenuNamePtr; + if (newMenuTypePtr == NULL) { + menuDupCommandArray[3] = Tcl_NewStringObj("normal", -1); + } else { + menuDupCommandArray[3] = newMenuTypePtr; + } + for (i = 0; i < 4; i++) { + Tcl_IncrRefCount(menuDupCommandArray[i]); + } + Tcl_Preserve(menuPtr); + returnResult = Tcl_EvalObjv(menuPtr->interp, 4, menuDupCommandArray, 0); + for (i = 0; i < 4; i++) { + Tcl_DecrRefCount(menuDupCommandArray[i]); + } + + /* + * Make sure the tcl command actually created the clone. + */ + + if ((returnResult == TCL_OK) && + ((menuRefPtr = TkFindMenuReferencesObj(menuPtr->interp, + newMenuNamePtr)) != NULL) + && (menuPtr->numEntries == menuRefPtr->menuPtr->numEntries)) { + TkMenu *newMenuPtr = menuRefPtr->menuPtr; + Tcl_Obj *newObjv[3]; + int i, numElements; + + /* + * Now put this newly created menu into the parent menu's instance + * chain. + */ + + if (menuPtr->nextInstancePtr == NULL) { + menuPtr->nextInstancePtr = newMenuPtr; + newMenuPtr->masterMenuPtr = menuPtr->masterMenuPtr; + } else { + TkMenu *masterMenuPtr; + + masterMenuPtr = menuPtr->masterMenuPtr; + newMenuPtr->nextInstancePtr = masterMenuPtr->nextInstancePtr; + masterMenuPtr->nextInstancePtr = newMenuPtr; + newMenuPtr->masterMenuPtr = masterMenuPtr; + } + + /* + * Add the master menu's window to the bind tags for this window after + * this window's tag. This is so the user can bind to either this + * clone (which may not be easy to do) or the entire menu clone + * structure. + */ + + newObjv[0] = Tcl_NewStringObj("bindtags", -1); + newObjv[1] = Tcl_NewStringObj(Tk_PathName(newMenuPtr->tkwin), -1); + Tcl_IncrRefCount(newObjv[0]); + Tcl_IncrRefCount(newObjv[1]); + if (Tk_BindtagsObjCmd(newMenuPtr->tkwin, newMenuPtr->interp, 2, + newObjv) == TCL_OK) { + const char *windowName; + Tcl_Obj *bindingsPtr = + Tcl_DuplicateObj(Tcl_GetObjResult(newMenuPtr->interp)); + Tcl_Obj *elementPtr; + + Tcl_IncrRefCount(bindingsPtr); + Tcl_ListObjLength(newMenuPtr->interp, bindingsPtr, &numElements); + for (i = 0; i < numElements; i++) { + Tcl_ListObjIndex(newMenuPtr->interp, bindingsPtr, i, + &elementPtr); + windowName = Tcl_GetString(elementPtr); + if (strcmp(windowName, Tk_PathName(newMenuPtr->tkwin)) + == 0) { + Tcl_Obj *newElementPtr = Tcl_NewStringObj( + Tk_PathName(newMenuPtr->masterMenuPtr->tkwin), -1); + + /* + * The newElementPtr will have its refCount incremented + * here, so we don't need to worry about it any more. + */ + + Tcl_ListObjReplace(menuPtr->interp, bindingsPtr, + i + 1, 0, 1, &newElementPtr); + newObjv[2] = bindingsPtr; + Tk_BindtagsObjCmd(newMenuPtr->tkwin, menuPtr->interp, 3, + newObjv); + break; + } + } + Tcl_DecrRefCount(bindingsPtr); + } + Tcl_DecrRefCount(newObjv[0]); + Tcl_DecrRefCount(newObjv[1]); + Tcl_ResetResult(menuPtr->interp); + + /* + * Clone all of the cascade menus that this menu points to. + */ + + for (i = 0; i < menuPtr->numEntries; i++) { + TkMenuReferences *cascadeRefPtr; + TkMenu *oldCascadePtr; + + if ((menuPtr->entries[i]->type == CASCADE_ENTRY) + && (menuPtr->entries[i]->namePtr != NULL)) { + cascadeRefPtr = + TkFindMenuReferencesObj(menuPtr->interp, + menuPtr->entries[i]->namePtr); + if ((cascadeRefPtr != NULL) && (cascadeRefPtr->menuPtr)) { + Tcl_Obj *windowNamePtr = + Tcl_NewStringObj(Tk_PathName(newMenuPtr->tkwin), + -1); + Tcl_Obj *newCascadePtr; + + oldCascadePtr = cascadeRefPtr->menuPtr; + + Tcl_IncrRefCount(windowNamePtr); + newCascadePtr = TkNewMenuName(menuPtr->interp, + windowNamePtr, oldCascadePtr); + Tcl_IncrRefCount(newCascadePtr); + CloneMenu(oldCascadePtr, newCascadePtr, NULL); + + newObjv[0] = Tcl_NewStringObj("-menu", -1); + newObjv[1] = newCascadePtr; + Tcl_IncrRefCount(newObjv[0]); + ConfigureMenuEntry(newMenuPtr->entries[i], 2, newObjv); + Tcl_DecrRefCount(newObjv[0]); + Tcl_DecrRefCount(newCascadePtr); + Tcl_DecrRefCount(windowNamePtr); + } + } + } + + returnResult = TCL_OK; + } else { + returnResult = TCL_ERROR; + } + Tcl_Release(menuPtr); + return returnResult; +} + +/* + *---------------------------------------------------------------------- + * + * MenuDoXPosition -- + * + * Given arguments from an option command line, returns the X position. + * + * Results: + * Returns TCL_OK or TCL_Error + * + * Side effects: + * xPosition is set to the X-position of the menu entry. + * + *---------------------------------------------------------------------- + */ + +static int +MenuDoXPosition( + Tcl_Interp *interp, + TkMenu *menuPtr, + Tcl_Obj *objPtr) +{ + int index; + + TkRecomputeMenu(menuPtr); + if (TkGetMenuIndex(interp, menuPtr, objPtr, 0, &index) != TCL_OK) { + return TCL_ERROR; + } + Tcl_ResetResult(interp); + if (index < 0) { + Tcl_SetObjResult(interp, Tcl_NewIntObj(0)); + } else { + Tcl_SetObjResult(interp, Tcl_NewIntObj(menuPtr->entries[index]->x)); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * MenuDoYPosition -- + * + * Given arguments from an option command line, returns the Y position. + * + * Results: + * Returns TCL_OK or TCL_Error + * + * Side effects: + * yPosition is set to the Y-position of the menu entry. + * + *---------------------------------------------------------------------- + */ + +static int +MenuDoYPosition( + Tcl_Interp *interp, + TkMenu *menuPtr, + Tcl_Obj *objPtr) +{ + int index; + + TkRecomputeMenu(menuPtr); + if (TkGetMenuIndex(interp, menuPtr, objPtr, 0, &index) != TCL_OK) { + goto error; + } + Tcl_ResetResult(interp); + if (index < 0) { + Tcl_SetObjResult(interp, Tcl_NewIntObj(0)); + } else { + Tcl_SetObjResult(interp, Tcl_NewIntObj(menuPtr->entries[index]->y)); + } + + return TCL_OK; + + error: + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * GetIndexFromCoords -- + * + * Given a string of the form "@integer", return the menu item + * corresponding to the provided y-coordinate in the menu window. + * + * Results: + * If int is a valid number, *indexPtr will be the number of the + * menuentry that is the correct height. If int is invalid, *indexPtr + * will be unchanged. Returns appropriate Tcl error number. + * + * Side effects: + * If int is invalid, interp's result will be set to NULL. + * + *---------------------------------------------------------------------- + */ + +static int +GetIndexFromCoords( + Tcl_Interp *interp, /* Interpreter of menu. */ + TkMenu *menuPtr, /* The menu we are searching. */ + const char *string, /* The @string we are parsing. */ + int *indexPtr) /* The index of the item that matches. */ +{ + int x, y, i; + const char *p; + char *end; + int x2, borderwidth, max; + + TkRecomputeMenu(menuPtr); + p = string + 1; + y = strtol(p, &end, 0); + if (end == p) { + goto error; + } + Tk_GetPixelsFromObj(interp, menuPtr->tkwin, + menuPtr->borderWidthPtr, &borderwidth); + if (*end == ',') { + x = y; + p = end + 1; + y = strtol(p, &end, 0); + if (end == p) { + goto error; + } + } else { + x = borderwidth; + } + + *indexPtr = -1; + + /* set the width of the final column to the remainder of the window + * being aware of windows that may not be mapped yet. + */ + max = Tk_IsMapped(menuPtr->tkwin) + ? Tk_Width(menuPtr->tkwin) : Tk_ReqWidth(menuPtr->tkwin); + max -= borderwidth; + + for (i = 0; i < menuPtr->numEntries; i++) { + if (menuPtr->entries[i]->entryFlags & ENTRY_LAST_COLUMN) { + x2 = max; + } else { + x2 = menuPtr->entries[i]->x + menuPtr->entries[i]->width; + } + if ((x >= menuPtr->entries[i]->x) && (y >= menuPtr->entries[i]->y) + && (x < x2) + && (y < (menuPtr->entries[i]->y + + menuPtr->entries[i]->height))) { + *indexPtr = i; + break; + } + } + return TCL_OK; + + error: + Tcl_ResetResult(interp); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * RecursivelyDeleteMenu -- + * + * Deletes a menu and any cascades underneath it. Used for deleting + * instances when a menu is no longer being used as a menubar, for + * instance. + * + * Results: + * None. + * + * Side effects: + * Destroys the menu and all cascade menus underneath it. + * + *---------------------------------------------------------------------- + */ + +static void +RecursivelyDeleteMenu( + TkMenu *menuPtr) /* The menubar instance we are deleting. */ +{ + int i; + TkMenuEntry *mePtr; + + /* + * It is not 100% clear that this preserve/release pair is required, but + * we have added them for safety in this very complex code. + */ + + Tcl_Preserve(menuPtr); + + for (i = 0; i < menuPtr->numEntries; i++) { + mePtr = menuPtr->entries[i]; + if ((mePtr->type == CASCADE_ENTRY) + && (mePtr->childMenuRefPtr != NULL) + && (mePtr->childMenuRefPtr->menuPtr != NULL)) { + RecursivelyDeleteMenu(mePtr->childMenuRefPtr->menuPtr); + } + } + if (menuPtr->tkwin != NULL) { + Tk_DestroyWindow(menuPtr->tkwin); + } + + Tcl_Release(menuPtr); +} + +/* + *---------------------------------------------------------------------- + * + * TkNewMenuName -- + * + * Makes a new unique name for a cloned menu. Will be a child of oldName. + * + * Results: + * Returns a char * which has been allocated; caller must free. + * + * Side effects: + * Memory is allocated. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +TkNewMenuName( + Tcl_Interp *interp, /* The interp the new name has to live in.*/ + Tcl_Obj *parentPtr, /* The prefix path of the new name. */ + TkMenu *menuPtr) /* The menu we are cloning. */ +{ + Tcl_Obj *resultPtr = NULL; /* Initialization needed only to prevent + * compiler warning. */ + Tcl_Obj *childPtr; + char *destString; + int i; + int doDot; + Tcl_HashTable *nameTablePtr = NULL; + TkWindow *winPtr = (TkWindow *) menuPtr->tkwin; + const char *parentName = Tcl_GetString(parentPtr); + + if (winPtr->mainPtr != NULL) { + nameTablePtr = &(winPtr->mainPtr->nameTable); + } + + doDot = parentName[strlen(parentName) - 1] != '.'; + + childPtr = Tcl_NewStringObj(Tk_PathName(menuPtr->tkwin), -1); + for (destString = Tcl_GetString(childPtr); + *destString != '\0'; destString++) { + if (*destString == '.') { + *destString = '#'; + } + } + + for (i = 0; ; i++) { + if (i == 0) { + resultPtr = Tcl_DuplicateObj(parentPtr); + if (doDot) { + Tcl_AppendToObj(resultPtr, ".", -1); + } + Tcl_AppendObjToObj(resultPtr, childPtr); + } else { + Tcl_Obj *intPtr; + + Tcl_DecrRefCount(resultPtr); + resultPtr = Tcl_DuplicateObj(parentPtr); + if (doDot) { + Tcl_AppendToObj(resultPtr, ".", -1); + } + Tcl_AppendObjToObj(resultPtr, childPtr); + intPtr = Tcl_NewIntObj(i); + Tcl_AppendObjToObj(resultPtr, intPtr); + Tcl_DecrRefCount(intPtr); + } + destString = Tcl_GetString(resultPtr); + if ((Tcl_FindCommand(interp, destString, NULL, 0) == NULL) + && ((nameTablePtr == NULL) + || (Tcl_FindHashEntry(nameTablePtr, destString) == NULL))) { + break; + } + } + Tcl_DecrRefCount(childPtr); + return resultPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TkSetWindowMenuBar -- + * + * Associates a menu with a window. Called by ConfigureFrame in in + * response to a "-menu .foo" configuration option for a top level. + * + * Results: + * None. + * + * Side effects: + * The old menu clones for the menubar are thrown away, and a handler is + * set up to allocate the new ones. + * + *---------------------------------------------------------------------- + */ + +void +TkSetWindowMenuBar( + Tcl_Interp *interp, /* The interpreter the toplevel lives in. */ + Tk_Window tkwin, /* The toplevel window. */ + const char *oldMenuName, /* The name of the menubar previously set in + * this toplevel. NULL means no menu was set + * previously. */ + const char *menuName) /* The name of the new menubar that the + * toplevel needs to be set to. NULL means + * that their is no menu now. */ +{ + TkMenuTopLevelList *topLevelListPtr, *prevTopLevelPtr; + TkMenu *menuPtr; + TkMenuReferences *menuRefPtr; + + /* + * Destroy the menubar instances of the old menu. Take this window out of + * the old menu's top level reference list. + */ + + if (oldMenuName != NULL) { + menuRefPtr = TkFindMenuReferences(interp, oldMenuName); + if (menuRefPtr != NULL) { + /* + * Find the menubar instance that is to be removed. Destroy it and + * all of the cascades underneath it. + */ + + if (menuRefPtr->menuPtr != NULL) { + TkMenu *instancePtr; + + menuPtr = menuRefPtr->menuPtr; + + for (instancePtr = menuPtr->masterMenuPtr; + instancePtr != NULL; + instancePtr = instancePtr->nextInstancePtr) { + if (instancePtr->menuType == MENUBAR + && instancePtr->parentTopLevelPtr == tkwin) { + RecursivelyDeleteMenu(instancePtr); + break; + } + } + } + + /* + * Now we need to remove this toplevel from the list of toplevels + * that reference this menu. + */ + + topLevelListPtr = menuRefPtr->topLevelListPtr; + prevTopLevelPtr = NULL; + + while ((topLevelListPtr != NULL) + && (topLevelListPtr->tkwin != tkwin)) { + prevTopLevelPtr = topLevelListPtr; + topLevelListPtr = topLevelListPtr->nextPtr; + } + + /* + * Now we have found the toplevel reference that matches the + * tkwin; remove this reference from the list. + */ + + if (topLevelListPtr != NULL) { + if (prevTopLevelPtr == NULL) { + menuRefPtr->topLevelListPtr = + menuRefPtr->topLevelListPtr->nextPtr; + } else { + prevTopLevelPtr->nextPtr = topLevelListPtr->nextPtr; + } + ckfree(topLevelListPtr); + TkFreeMenuReferences(menuRefPtr); + } + } + } + + /* + * Now, add the clone references for the new menu. + */ + + if (menuName != NULL && menuName[0] != 0) { + TkMenu *menuBarPtr = NULL; + + menuRefPtr = TkCreateMenuReferences(interp, menuName); + + menuPtr = menuRefPtr->menuPtr; + if (menuPtr != NULL) { + Tcl_Obj *cloneMenuPtr; + TkMenuReferences *cloneMenuRefPtr; + Tcl_Obj *newObjv[4]; + Tcl_Obj *windowNamePtr = Tcl_NewStringObj(Tk_PathName(tkwin), + -1); + Tcl_Obj *menubarPtr = Tcl_NewStringObj("menubar", -1); + + /* + * Clone the menu and all of the cascades underneath it. + */ + + Tcl_IncrRefCount(windowNamePtr); + cloneMenuPtr = TkNewMenuName(interp, windowNamePtr, + menuPtr); + Tcl_IncrRefCount(cloneMenuPtr); + Tcl_IncrRefCount(menubarPtr); + CloneMenu(menuPtr, cloneMenuPtr, menubarPtr); + + cloneMenuRefPtr = TkFindMenuReferencesObj(interp, cloneMenuPtr); + if ((cloneMenuRefPtr != NULL) + && (cloneMenuRefPtr->menuPtr != NULL)) { + Tcl_Obj *cursorPtr = Tcl_NewStringObj("-cursor", -1); + Tcl_Obj *nullPtr = Tcl_NewObj(); + + cloneMenuRefPtr->menuPtr->parentTopLevelPtr = tkwin; + menuBarPtr = cloneMenuRefPtr->menuPtr; + newObjv[0] = cursorPtr; + newObjv[1] = nullPtr; + Tcl_IncrRefCount(cursorPtr); + Tcl_IncrRefCount(nullPtr); + ConfigureMenu(menuPtr->interp, cloneMenuRefPtr->menuPtr, + 2, newObjv); + Tcl_DecrRefCount(cursorPtr); + Tcl_DecrRefCount(nullPtr); + } + + TkpSetWindowMenuBar(tkwin, menuBarPtr); + Tcl_DecrRefCount(cloneMenuPtr); + Tcl_DecrRefCount(menubarPtr); + Tcl_DecrRefCount(windowNamePtr); + } else { + TkpSetWindowMenuBar(tkwin, NULL); + } + + /* + * Add this window to the menu's list of windows that refer to this + * menu. + */ + + topLevelListPtr = ckalloc(sizeof(TkMenuTopLevelList)); + topLevelListPtr->tkwin = tkwin; + topLevelListPtr->nextPtr = menuRefPtr->topLevelListPtr; + menuRefPtr->topLevelListPtr = topLevelListPtr; + } else { + TkpSetWindowMenuBar(tkwin, NULL); + } + TkpSetMainMenubar(interp, tkwin, menuName); +} + +/* + *---------------------------------------------------------------------- + * + * DestroyMenuHashTable -- + * + * Called when an interp is deleted and a menu hash table has been set in + * it. + * + * Results: + * None. + * + * Side effects: + * The hash table is destroyed. + * + *---------------------------------------------------------------------- + */ + +static void +DestroyMenuHashTable( + ClientData clientData, /* The menu hash table we are destroying. */ + Tcl_Interp *interp) /* The interpreter we are destroying. */ +{ + Tcl_DeleteHashTable(clientData); + ckfree(clientData); +} + +/* + *---------------------------------------------------------------------- + * + * TkGetMenuHashTable -- + * + * For a given interp, give back the menu hash table that goes with it. + * If the hash table does not exist, it is created. + * + * Results: + * Returns a hash table pointer. + * + * Side effects: + * A new hash table is created if there were no table in the interp + * originally. + * + *---------------------------------------------------------------------- + */ + +Tcl_HashTable * +TkGetMenuHashTable( + Tcl_Interp *interp) /* The interp we need the hash table in.*/ +{ + Tcl_HashTable *menuTablePtr = + Tcl_GetAssocData(interp, MENU_HASH_KEY, NULL); + + if (menuTablePtr == NULL) { + menuTablePtr = ckalloc(sizeof(Tcl_HashTable)); + Tcl_InitHashTable(menuTablePtr, TCL_STRING_KEYS); + Tcl_SetAssocData(interp, MENU_HASH_KEY, DestroyMenuHashTable, + menuTablePtr); + } + return menuTablePtr; +} + +/* + *---------------------------------------------------------------------- + * + * TkCreateMenuReferences -- + * + * Given a pathname, gives back a pointer to a TkMenuReferences + * structure. If a reference is not already in the hash table, one is + * created. + * + * Results: + * Returns a pointer to a menu reference structure. Should not be freed + * by calller; when a field of the reference is cleared, + * TkFreeMenuReferences should be called. + * + * Side effects: + * A new hash table entry is created if there were no references to the + * menu originally. + * + *---------------------------------------------------------------------- + */ + +TkMenuReferences * +TkCreateMenuReferences( + Tcl_Interp *interp, + const char *pathName) /* The path of the menu widget. */ +{ + Tcl_HashEntry *hashEntryPtr; + TkMenuReferences *menuRefPtr; + int newEntry; + Tcl_HashTable *menuTablePtr = TkGetMenuHashTable(interp); + + hashEntryPtr = Tcl_CreateHashEntry(menuTablePtr, pathName, &newEntry); + if (newEntry) { + menuRefPtr = ckalloc(sizeof(TkMenuReferences)); + menuRefPtr->menuPtr = NULL; + menuRefPtr->topLevelListPtr = NULL; + menuRefPtr->parentEntryPtr = NULL; + menuRefPtr->hashEntryPtr = hashEntryPtr; + Tcl_SetHashValue(hashEntryPtr, menuRefPtr); + } else { + menuRefPtr = Tcl_GetHashValue(hashEntryPtr); + } + return menuRefPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TkFindMenuReferences -- + * + * Given a pathname, gives back a pointer to the TkMenuReferences + * structure. + * + * Results: + * Returns a pointer to a menu reference structure. Should not be freed + * by calller; when a field of the reference is cleared, + * TkFreeMenuReferences should be called. Returns NULL if no reference + * with this pathname exists. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +TkMenuReferences * +TkFindMenuReferences( + Tcl_Interp *interp, /* The interp the menu is living in. */ + const char *pathName) /* The path of the menu widget. */ +{ + Tcl_HashEntry *hashEntryPtr; + TkMenuReferences *menuRefPtr = NULL; + Tcl_HashTable *menuTablePtr; + + menuTablePtr = TkGetMenuHashTable(interp); + hashEntryPtr = Tcl_FindHashEntry(menuTablePtr, pathName); + if (hashEntryPtr != NULL) { + menuRefPtr = Tcl_GetHashValue(hashEntryPtr); + } + return menuRefPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TkFindMenuReferencesObj -- + * + * Given a pathname, gives back a pointer to the TkMenuReferences + * structure. + * + * Results: + * Returns a pointer to a menu reference structure. Should not be freed + * by calller; when a field of the reference is cleared, + * TkFreeMenuReferences should be called. Returns NULL if no reference + * with this pathname exists. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +TkMenuReferences * +TkFindMenuReferencesObj( + Tcl_Interp *interp, /* The interp the menu is living in. */ + Tcl_Obj *objPtr) /* The path of the menu widget. */ +{ + const char *pathName = Tcl_GetString(objPtr); + + return TkFindMenuReferences(interp, pathName); +} + +/* + *---------------------------------------------------------------------- + * + * TkFreeMenuReferences -- + * + * This is called after one of the fields in a menu reference is cleared. + * It cleans up the ref if it is now empty. + * + * Results: + * Returns 1 if the references structure was freed, and 0 otherwise. + * + * Side effects: + * If this is the last field to be cleared, the menu ref is taken out of + * the hash table. + * + *---------------------------------------------------------------------- + */ + +int +TkFreeMenuReferences( + TkMenuReferences *menuRefPtr) + /* The menu reference to free. */ +{ + if ((menuRefPtr->menuPtr == NULL) + && (menuRefPtr->parentEntryPtr == NULL) + && (menuRefPtr->topLevelListPtr == NULL)) { + Tcl_DeleteHashEntry(menuRefPtr->hashEntryPtr); + ckfree(menuRefPtr); + return 1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * DeleteMenuCloneEntries -- + * + * For every clone in this clone chain, delete the menu entries given by + * the parameters. + * + * Results: + * None. + * + * Side effects: + * The appropriate entries are deleted from all clones of this menu. + * + *---------------------------------------------------------------------- + */ + +static void +DeleteMenuCloneEntries( + TkMenu *menuPtr, /* The menu the command was issued with. */ + int first, /* The zero-based first entry in the set of + * entries to delete. */ + int last) /* The zero-based last entry. */ +{ + TkMenu *menuListPtr; + int numDeleted, i, j; + + numDeleted = last + 1 - first; + for (menuListPtr = menuPtr->masterMenuPtr; menuListPtr != NULL; + menuListPtr = menuListPtr->nextInstancePtr) { + for (i = last; i >= first; i--) { + Tcl_EventuallyFree(menuListPtr->entries[i], (Tcl_FreeProc *) DestroyMenuEntry); + } + for (i = last + 1; i < menuListPtr->numEntries; i++) { + j = i - numDeleted; + menuListPtr->entries[j] = menuListPtr->entries[i]; + menuListPtr->entries[j]->index = j; + } + menuListPtr->numEntries -= numDeleted; + if (menuListPtr->numEntries == 0) { + ckfree(menuListPtr->entries); + menuListPtr->entries = NULL; + } + if ((menuListPtr->active >= first) + && (menuListPtr->active <= last)) { + menuListPtr->active = -1; + } else if (menuListPtr->active > last) { + menuListPtr->active -= numDeleted; + } + TkEventuallyRecomputeMenu(menuListPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkMenuCleanup -- + * + * Resets menusInitialized to allow Tk to be finalized and reused without + * the DLL being unloaded. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +TkMenuCleanup( + ClientData unused) +{ + menusInitialized = 0; +} + +/* + *---------------------------------------------------------------------- + * + * TkMenuInit -- + * + * Sets up the hash tables and the variables used by the menu package. + * + * Results: + * None. + * + * Side effects: + * lastMenuID gets initialized, and the parent hash and the command hash + * are allocated. + * + *---------------------------------------------------------------------- + */ + +void +TkMenuInit(void) +{ + ThreadSpecificData *tsdPtr = + Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); + + if (!menusInitialized) { + Tcl_MutexLock(&menuMutex); + if (!menusInitialized) { + TkpMenuInit(); + menusInitialized = 1; + } + + /* + * Make sure we cleanup on finalize. + */ + + TkCreateExitHandler((Tcl_ExitProc *) TkMenuCleanup, NULL); + Tcl_MutexUnlock(&menuMutex); + } + if (!tsdPtr->menusInitialized) { + TkpMenuThreadInit(); + tsdPtr->menuOptionTable = + Tk_CreateOptionTable(NULL, tkMenuConfigSpecs); + tsdPtr->entryOptionTables[TEAROFF_ENTRY] = + Tk_CreateOptionTable(NULL, specsArray[TEAROFF_ENTRY]); + tsdPtr->entryOptionTables[COMMAND_ENTRY] = + Tk_CreateOptionTable(NULL, specsArray[COMMAND_ENTRY]); + tsdPtr->entryOptionTables[CASCADE_ENTRY] = + Tk_CreateOptionTable(NULL, specsArray[CASCADE_ENTRY]); + tsdPtr->entryOptionTables[SEPARATOR_ENTRY] = + Tk_CreateOptionTable(NULL, specsArray[SEPARATOR_ENTRY]); + tsdPtr->entryOptionTables[RADIO_BUTTON_ENTRY] = + Tk_CreateOptionTable(NULL, specsArray[RADIO_BUTTON_ENTRY]); + tsdPtr->entryOptionTables[CHECK_BUTTON_ENTRY] = + Tk_CreateOptionTable(NULL, specsArray[CHECK_BUTTON_ENTRY]); + tsdPtr->menusInitialized = 1; + } +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |