/*
 * 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 "main", 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 main 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 {
    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 initialized. 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 = NULL;
    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 main 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 == -1) || (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, index = -1;

	if (objc != 4 && objc != 5) {
	    Tcl_WrongNumArgs(interp, 2, objv, "x y ?index?");
	    goto error;
	}
	if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK)
		|| (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) {
	    goto error;
	}
	if (objc == 5) {
            if (TkGetMenuIndex(interp, menuPtr, objv[4], 0, &index) != TCL_OK) {
                goto error;
            }
	}

	/*
	 * Tearoff menus are the same as ordinary menus on the Mac and are
	 * posted differently on 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 menus 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, index);
	} else {
	    result = TkpPostTearoffMenu(interp, menuPtr, x, y, index);
	}
	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, TCL_EVAL_GLOBAL);
	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 *parentMainMenuPtr;
    TkMenuEntry *parentMainEntryPtr;
    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 main menus back to
     * this entry name if this is a main menu pointed to by another main
     * 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 main 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);

	    parentMainMenuPtr = cascadePtr->menuPtr->masterMenuPtr;
	    parentMainEntryPtr =
		    parentMainMenuPtr->entries[cascadePtr->index];
	    newObjv[0] = menuNamePtr;
	    newObjv[1] = parentMainEntryPtr->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 main 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 main instance, destroys all of the
 *	instances. If called on a non-main 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 == MAIN_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)) {

	    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 {
	Tcl_GetStringFromObj(mePtr->labelPtr, &mePtr->labelLength);
    }
    if (mePtr->accelPtr == NULL) {
	mePtr->accelLength = 0;
    } else {
	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;

	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 main
     * 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_GetIntFromObj(interp, objPtr, &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_StringCaseMatch(label, string, 0))) {
	    *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 main 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 (Tcl_InterpDeleted(interp) || (mePtr->namePtr == NULL)) {
	/*
	 * Do nothing if the interpreter is going away or we have
	 * no variable name.
	 */

    	return NULL;
    }

    menuPtr = mePtr->menuPtr;

    if (menuPtr->menuFlags & MENU_DELETION_PENDING) {
    	return NULL;
    }

    name = Tcl_GetString(mePtr->namePtr);

    /*
     * If the variable is being unset, then re-establish the trace.
     */

    if (flags & TCL_TRACE_UNSETS) {
        ClientData probe = NULL;
	mePtr->entryFlags &= ~ENTRY_SELECTED;

        do {
                probe = Tcl_VarTraceInfo(interp, name,
                        TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
                        MenuVarProc, probe);
                if (probe == (ClientData)mePtr) {
                    break;
                }
        } while (probe);
        if (probe) {
                /*
                 * We were able to fetch the unset trace for our
                 * namePtr, which means it is not unset and not
                 * the cause of this unset trace. Instead some outdated
                 * former variable must be, and we should ignore it.
                 */
		return NULL;
        }
	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 = MAIN_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 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 main 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:
 */
