Logo Search packages:      
Sourcecode: saods9 version File versions

tkMenu.c

/* 
 * 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.
 *
 * RCS: @(#) $Id: tkMenu.c,v 1.1.1.1 2004/04/02 22:35:07 joye Exp $
 */

/*
 * Notes on implementation of menus:
 *
 * Menus can be used in three ways:
 * - as a popup menu, either as part of a menubutton or standalone.
 * - as a menubar. The menu's cascade items are arranged according to
 * the specific platform to provide the user access to the menus at all
 * times
 * - as a tearoff palette. This is a window with the menu's items in it.
 *
 * The goal is to provide the Tk developer with a way to use a common
 * set of menus for all of these tasks.
 *
 * In order to make the bindings for cascade menus work properly under Unix,
 * the cascade menus' pathnames must be proper children of the menu that
 * they are cascade from. So if there is a menu .m, and it has two
 * cascades labelled "File" and "Edit", the cascade menus might have
 * the pathnames .m.file and .m.edit. Another constraint is that the menus
 * used for menubars must be children of the toplevel widget that they
 * are attached to. And on the Macintosh, the platform specific menu handle
 * for cascades attached to a menu bar must have a title that matches the
 * label for the cascade menu.
 *
 * To handle all of the constraints, Tk menubars and tearoff menus are
 * implemented using menu clones. Menu clones are full menus in their own
 * right; they have a Tk window and pathname associated with them; they have
 * a TkMenu structure and array of entries. However, they are linked with the
 * original menu that they were cloned from. The reflect the attributes of
 * the original, or "master", menu. So if an item is added to a menu, and
 * that menu has clones, then the item must be added to all of its clones
 * also. Menus are cloned when a menu is torn-off or when a menu is assigned
 * as a menubar using the "-menu" option of the toplevel's pathname configure
 * subcommand. When a clone is destroyed, only the clone is destroyed, but
 * when the master menu is destroyed, all clones are also destroyed. This
 * allows the developer to just deal with one set of menus when creating
 * and destroying.
 *
 * Clones are rather tricky when a menu with cascade entries is cloned (such
 * as a menubar). Not only does the menu have to be cloned, but each cascade
 * entry's corresponding menu must also be cloned. This maintains the pathname
 * parent-child hierarchy necessary for menubars and toplevels to work.
 * This leads to several special cases:
 *
 * 1. When a new menu is created, and it is pointed to by cascade entries in
 * cloned menus, the new menu has to be cloned to parallel the cascade
 * structure.
 * 2. When a cascade item is added to a menu that has been cloned, and the
 * menu that the cascade item points to exists, that menu has to be cloned.
 * 3. When the menu that a cascade entry points to is changed, the old
 * cloned cascade menu has to be discarded, and the new one has to be cloned.
 *
 */

#if 0

/*
 * used only to test for old config code
 */

#define __NO_OLD_CONFIG
#endif

#include "tkPort.h"
#include "tkMenu.h"

#define MENU_HASH_KEY "tkMenus"

typedef struct ThreadSpecificData {
    int menusInitialized;       /* Flag indicates whether thread-specific
                         * elements of the Windows Menu module
                         * have been initialized. */
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;

/*
 * The following flag indicates whether the process-wide state for
 * the Menu module has been intialized.  The Mutex protects access to
 * that flag.
 */

static int menusInitialized;
TCL_DECLARE_MUTEX(menuMutex)

/*
 * Configuration specs for individual menu entries. If this changes, be sure
 * to update code in TkpMenuInit that changes the font string entry.
 */

char *tkMenuStateStrings[] = {"active", "normal", "disabled", (char *) NULL};

static CONST char *menuEntryTypeStrings[] = {
    "cascade", "checkbutton", "command", "radiobutton", "separator",
    (char *) NULL
};

/*
 * The following table defines the legal values for the -compound option.
 * It is used with the "enum compound" declaration in tkMenu.h
 */

static char *compoundStrings[] = {
    "bottom", "center", "left", "none", "right", "top", (char *) NULL
};

Tk_OptionSpec tkBasicMenuEntryConfigSpecs[] = {
    {TK_OPTION_BORDER, "-activebackground", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_ACTIVE_BG, Tk_Offset(TkMenuEntry, activeBorderPtr), -1, 
      TK_OPTION_NULL_OK},
    {TK_OPTION_COLOR, "-activeforeground", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_ACTIVE_FG,
      Tk_Offset(TkMenuEntry, activeFgPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_STRING, "-accelerator", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_ACCELERATOR,
      Tk_Offset(TkMenuEntry, accelPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_BORDER, "-background", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_BG,
      Tk_Offset(TkMenuEntry, borderPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_BITMAP, "-bitmap", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_BITMAP,
      Tk_Offset(TkMenuEntry, bitmapPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_BOOLEAN, "-columnbreak", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_COLUMN_BREAK,
      -1, Tk_Offset(TkMenuEntry, columnBreak)},
    {TK_OPTION_STRING, "-command", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_COMMAND,
      Tk_Offset(TkMenuEntry, commandPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_STRING_TABLE, "-compound", "compound", "Compound",
        DEF_MENU_ENTRY_COMPOUND, -1, Tk_Offset(TkMenuEntry, compound), 0,
      (ClientData) compoundStrings, 0},
    {TK_OPTION_FONT, "-font", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_FONT,
      Tk_Offset(TkMenuEntry, fontPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_COLOR, "-foreground", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_FG,
      Tk_Offset(TkMenuEntry, fgPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_BOOLEAN, "-hidemargin", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_HIDE_MARGIN,
      -1, Tk_Offset(TkMenuEntry, hideMargin)},
    {TK_OPTION_STRING, "-image", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_IMAGE,
      Tk_Offset(TkMenuEntry, imagePtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_STRING, "-label", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_LABEL,
      Tk_Offset(TkMenuEntry, labelPtr), -1, 0},
    {TK_OPTION_STRING_TABLE, "-state", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_STATE,
      -1, Tk_Offset(TkMenuEntry, state), 0,
      (ClientData) tkMenuStateStrings},
    {TK_OPTION_INT, "-underline", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_UNDERLINE, -1, Tk_Offset(TkMenuEntry, underline)},
    {TK_OPTION_END}
};

Tk_OptionSpec tkSeparatorEntryConfigSpecs[] = {
    {TK_OPTION_BORDER, "-background", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_BG,
      Tk_Offset(TkMenuEntry, borderPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_END}
};

Tk_OptionSpec tkCheckButtonEntryConfigSpecs[] = {
    {TK_OPTION_BOOLEAN, "-indicatoron", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_INDICATOR,
      -1, Tk_Offset(TkMenuEntry, indicatorOn)},
    {TK_OPTION_STRING, "-offvalue", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_OFF_VALUE,
      Tk_Offset(TkMenuEntry, offValuePtr), -1},
    {TK_OPTION_STRING, "-onvalue", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_ON_VALUE,
      Tk_Offset(TkMenuEntry, onValuePtr), -1},
    {TK_OPTION_COLOR, "-selectcolor", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_SELECT,
      Tk_Offset(TkMenuEntry, indicatorFgPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_STRING, "-selectimage", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_SELECT_IMAGE,
      Tk_Offset(TkMenuEntry, selectImagePtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_STRING, "-variable", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_CHECK_VARIABLE,
      Tk_Offset(TkMenuEntry, namePtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
      (char *) NULL, 0, -1, 0, (ClientData) tkBasicMenuEntryConfigSpecs}
};

Tk_OptionSpec tkRadioButtonEntryConfigSpecs[] = {
    {TK_OPTION_BOOLEAN, "-indicatoron", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_INDICATOR,
      -1, Tk_Offset(TkMenuEntry, indicatorOn)},
    {TK_OPTION_COLOR, "-selectcolor", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_SELECT,
      Tk_Offset(TkMenuEntry, indicatorFgPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_STRING, "-selectimage", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_SELECT_IMAGE, 
      Tk_Offset(TkMenuEntry, selectImagePtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_STRING, "-value", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_VALUE,
      Tk_Offset(TkMenuEntry, onValuePtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_STRING, "-variable", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_RADIO_VARIABLE,
      Tk_Offset(TkMenuEntry, namePtr), -1, 0},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
      (char *) NULL, 0, -1, 0, (ClientData) tkBasicMenuEntryConfigSpecs}
};

Tk_OptionSpec tkCascadeEntryConfigSpecs[] = {
    {TK_OPTION_STRING, "-menu", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_MENU,
      Tk_Offset(TkMenuEntry, namePtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
      (char *) NULL, 0, -1, 0, (ClientData) tkBasicMenuEntryConfigSpecs}
};

Tk_OptionSpec tkTearoffEntryConfigSpecs[] = {
    {TK_OPTION_BORDER, "-background", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_BG,
      Tk_Offset(TkMenuEntry, borderPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_STRING_TABLE, "-state", (char *) NULL, (char *) NULL,
      DEF_MENU_ENTRY_STATE, -1, Tk_Offset(TkMenuEntry, state), 0,
      (ClientData) tkMenuStateStrings},
    {TK_OPTION_END}
};

static Tk_OptionSpec *specsArray[] = {
    tkCascadeEntryConfigSpecs, tkCheckButtonEntryConfigSpecs,
    tkBasicMenuEntryConfigSpecs, tkRadioButtonEntryConfigSpecs,
    tkSeparatorEntryConfigSpecs, tkTearoffEntryConfigSpecs};
    
/*
 * Menu type strings for use with Tcl_GetIndexFromObj.
 */

static CONST char *menuTypeStrings[] = {"normal", "tearoff", "menubar",
      (char *) NULL};

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},
    {TK_OPTION_PIXELS, "-activeborderwidth", "activeBorderWidth",
        "BorderWidth", DEF_MENU_ACTIVE_BORDER_WIDTH,
        Tk_Offset(TkMenu, activeBorderWidthPtr), -1},
    {TK_OPTION_COLOR, "-activeforeground", "activeForeground", 
      "Background", DEF_MENU_ACTIVE_FG_COLOR, 
      Tk_Offset(TkMenu, activeFgPtr), -1, 0,
      (ClientData) DEF_MENU_ACTIVE_FG_MONO},
    {TK_OPTION_BORDER, "-background", "background", "Background",
      DEF_MENU_BG_COLOR, Tk_Offset(TkMenu, borderPtr), -1, 0,
      (ClientData) DEF_MENU_BG_MONO},
    {TK_OPTION_SYNONYM, "-bd", (char *) NULL, (char *) NULL,
      (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth"},
    {TK_OPTION_SYNONYM, "-bg", (char *) NULL, (char *) NULL,
      (char *) NULL, 0, -1, 0, (ClientData) "-background"},
    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
      DEF_MENU_BORDER_WIDTH,
      Tk_Offset(TkMenu, borderWidthPtr), -1, 0},
    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
      DEF_MENU_CURSOR,
      Tk_Offset(TkMenu, cursorPtr), -1, TK_OPTION_NULL_OK},
    {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},
    {TK_OPTION_SYNONYM, "-fg", (char *) NULL, (char *) NULL,
      (char *) NULL, 0, -1, 0, (ClientData) "-foreground"},
    {TK_OPTION_FONT, "-font", "font", "Font",
      DEF_MENU_FONT, Tk_Offset(TkMenu, fontPtr), -1},
    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
      DEF_MENU_FG, Tk_Offset(TkMenu, fgPtr), -1},
    {TK_OPTION_STRING, "-postcommand", "postCommand", "Command",
      DEF_MENU_POST_COMMAND, 
      Tk_Offset(TkMenu, postCommandPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
      DEF_MENU_RELIEF, Tk_Offset(TkMenu, reliefPtr), -1},
    {TK_OPTION_COLOR, "-selectcolor", "selectColor", "Background",
      DEF_MENU_SELECT_COLOR, Tk_Offset(TkMenu, indicatorFgPtr), -1, 0,
      (ClientData) DEF_MENU_SELECT_MONO},
    {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
      DEF_MENU_TAKE_FOCUS,
      Tk_Offset(TkMenu, takeFocusPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_BOOLEAN, "-tearoff", "tearOff", "TearOff",
      DEF_MENU_TEAROFF, -1, Tk_Offset(TkMenu, tearoff)},
    {TK_OPTION_STRING, "-tearoffcommand", "tearOffCommand", 
      "TearOffCommand", DEF_MENU_TEAROFF_CMD,
      Tk_Offset(TkMenu, tearoffCommandPtr), -1, TK_OPTION_NULL_OK},
    {TK_OPTION_STRING, "-title", "title", "Title",
      DEF_MENU_TITLE,  Tk_Offset(TkMenu, titlePtr), -1,
      TK_OPTION_NULL_OK},
    {TK_OPTION_STRING_TABLE, "-type", "type", "Type",
      DEF_MENU_TYPE, Tk_Offset(TkMenu, menuTypePtr), -1, TK_OPTION_NULL_OK,
      (ClientData) menuTypeStrings},
    {TK_OPTION_END}
};

/*
 * Command line options. Put here because MenuCmd has to look at them
 * along with MenuWidgetObjCmd.
 */

static CONST char *menuOptions[] = {
    "activate", "add", "cget", "clone", "configure", "delete", "entrycget",
    "entryconfigure", "index", "insert", "invoke", "post", "postcascade",
    "type", "unpost", "yposition", (char *) 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_YPOSITION
};

/*
 * Prototypes for static procedures in this file:
 */

static int        CloneMenu _ANSI_ARGS_((TkMenu *menuPtr,
                      Tcl_Obj *newMenuName, Tcl_Obj *newMenuTypeString));
static int        ConfigureMenu _ANSI_ARGS_((Tcl_Interp *interp,
                      TkMenu *menuPtr, int objc, Tcl_Obj *CONST objv[]));
static int        ConfigureMenuCloneEntries _ANSI_ARGS_((
                      Tcl_Interp *interp, TkMenu *menuPtr, int index,
                      int objc, Tcl_Obj *CONST objv[]));
static int        ConfigureMenuEntry _ANSI_ARGS_((TkMenuEntry *mePtr,
                      int objc, Tcl_Obj *CONST objv[]));
static void       DeleteMenuCloneEntries _ANSI_ARGS_((TkMenu *menuPtr,
                      int first, int last));
static void       DestroyMenuHashTable _ANSI_ARGS_((
                      ClientData clientData, Tcl_Interp *interp));
static void       DestroyMenuInstance _ANSI_ARGS_((TkMenu *menuPtr));
static void       DestroyMenuEntry _ANSI_ARGS_((char *memPtr));
static int        GetIndexFromCoords
                      _ANSI_ARGS_((Tcl_Interp *interp, TkMenu *menuPtr,
                      char *string, int *indexPtr));
static int        MenuDoYPosition _ANSI_ARGS_((Tcl_Interp *interp,
                      TkMenu *menuPtr, Tcl_Obj *objPtr));
static int        MenuAddOrInsert _ANSI_ARGS_((Tcl_Interp *interp,
                      TkMenu *menuPtr, Tcl_Obj *indexPtr, int objc,
                      Tcl_Obj *CONST objv[]));
static int        MenuCmd _ANSI_ARGS_((ClientData clientData,
                      Tcl_Interp *interp, int objc, 
                      Tcl_Obj *CONST objv[]));
static void       MenuCmdDeletedProc _ANSI_ARGS_((
                      ClientData clientData));
static TkMenuEntry *    MenuNewEntry _ANSI_ARGS_((TkMenu *menuPtr, int index,
                      int type));
static char *           MenuVarProc _ANSI_ARGS_((ClientData clientData,
                      Tcl_Interp *interp, CONST char *name1,
                      CONST char *name2, int flags));
static int        MenuWidgetObjCmd _ANSI_ARGS_((ClientData clientData,
                      Tcl_Interp *interp, int objc, 
                      Tcl_Obj *CONST objv[]));
static void       MenuWorldChanged _ANSI_ARGS_((
                      ClientData instanceData));
static int        PostProcessEntry _ANSI_ARGS_((TkMenuEntry *mePtr));
static void       RecursivelyDeleteMenu _ANSI_ARGS_((TkMenu *menuPtr));
static void       UnhookCascadeEntry _ANSI_ARGS_((TkMenuEntry *mePtr));
static void       TkMenuCleanup _ANSI_ARGS_((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 Tk_ClassProcs menuClass = {
    sizeof(Tk_ClassProcs),    /* size */
    MenuWorldChanged          /* worldChangedProc */
};

/*
 *--------------------------------------------------------------
 *
 * TkCreateMenuCmd --
 *
 *    Called by Tk at initialization time to create the menu
 *    command.
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side effects:
 *    See the user documentation.
 *
 *--------------------------------------------------------------
 */

int
TkCreateMenuCmd(interp)
    Tcl_Interp *interp;       /* Interpreter we are creating the 
                         * command in. */
{
    TkMenuOptionTables *optionTablesPtr = 
          (TkMenuOptionTables *) ckalloc(sizeof(TkMenuOptionTables));

    optionTablesPtr->menuOptionTable = 
          Tk_CreateOptionTable(interp, tkMenuConfigSpecs);
    optionTablesPtr->entryOptionTables[TEAROFF_ENTRY] =
          Tk_CreateOptionTable(interp, specsArray[TEAROFF_ENTRY]);
    optionTablesPtr->entryOptionTables[COMMAND_ENTRY] =
          Tk_CreateOptionTable(interp, specsArray[COMMAND_ENTRY]);
    optionTablesPtr->entryOptionTables[CASCADE_ENTRY] =
          Tk_CreateOptionTable(interp, specsArray[CASCADE_ENTRY]);
    optionTablesPtr->entryOptionTables[SEPARATOR_ENTRY] =
          Tk_CreateOptionTable(interp, specsArray[SEPARATOR_ENTRY]);
    optionTablesPtr->entryOptionTables[RADIO_BUTTON_ENTRY] =
          Tk_CreateOptionTable(interp, specsArray[RADIO_BUTTON_ENTRY]);
    optionTablesPtr->entryOptionTables[CHECK_BUTTON_ENTRY] =
          Tk_CreateOptionTable(interp, specsArray[CHECK_BUTTON_ENTRY]);

    Tcl_CreateObjCommand(interp, "menu", MenuCmd,
          (ClientData) optionTablesPtr, NULL);

    if (Tcl_IsSafe(interp)) {
      Tcl_HideCommand(interp, "menu", "menu");
    }

    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * MenuCmd --
 *
 *    This procedure 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.
 *
 *--------------------------------------------------------------
 */

static int
MenuCmd(clientData, interp, objc, objv)
    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 = Tk_MainWindow(interp);
    Tk_Window new;
    register TkMenu *menuPtr;
    TkMenuReferences *menuRefPtr;
    int i, index;
    int toplevel;
    char *windowName;
    static CONST char *typeStringList[] = {"-type", (char *) NULL};
    TkMenuOptionTables *optionTablesPtr = (TkMenuOptionTables *) clientData;

    if (objc < 2) {
      Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
      return TCL_ERROR;
    }

    TkMenuInit();

    toplevel = 1;
    for (i = 2; i < (objc - 1); i++) {
      if (Tcl_GetIndexFromObj(NULL, objv[i], typeStringList, NULL, 0, &index)
            != TCL_ERROR) {
          if ((Tcl_GetIndexFromObj(NULL, objv[i + 1], menuTypeStrings, NULL,
                0, &index) == TCL_OK) && (index == MENUBAR)) {
            toplevel = 0;
          }
          break;
      }
    }

    windowName = Tcl_GetStringFromObj(objv[1], NULL);
    new = Tk_CreateWindowFromPath(interp, tkwin, windowName, toplevel ? ""
          : NULL);
    if (new == 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 = (TkMenu *) ckalloc(sizeof(TkMenu));
    menuPtr->tkwin = new;
    menuPtr->display = Tk_Display(new);
    menuPtr->interp = interp;
    menuPtr->widgetCmd = Tcl_CreateObjCommand(interp,
          Tk_PathName(menuPtr->tkwin), MenuWidgetObjCmd,
          (ClientData) menuPtr, MenuCmdDeletedProc);
    menuPtr->entries = NULL;
    menuPtr->numEntries = 0;
    menuPtr->active = -1;
    menuPtr->borderPtr = NULL;
    menuPtr->borderWidthPtr = NULL;
    menuPtr->reliefPtr = NULL;
    menuPtr->activeBorderPtr = NULL;
    menuPtr->activeBorderWidthPtr = NULL;
    menuPtr->fontPtr = NULL;
    menuPtr->fgPtr = NULL;
    menuPtr->disabledFgPtr = NULL;
    menuPtr->activeFgPtr = NULL;
    menuPtr->indicatorFgPtr = NULL;
    menuPtr->tearoff = 0;
    menuPtr->tearoffCommandPtr = NULL;
    menuPtr->cursorPtr = None;
    menuPtr->takeFocusPtr = NULL;
    menuPtr->postCommandPtr = NULL;
    menuPtr->postCommandGeneration = 0;
    menuPtr->postedCascade = NULL;
    menuPtr->nextInstancePtr = NULL;
    menuPtr->masterMenuPtr = menuPtr;
    menuPtr->menuType = UNKNOWN_TYPE;
    menuPtr->menuFlags = 0;
    menuPtr->parentTopLevelPtr = NULL;
    menuPtr->menuTypePtr = NULL;
    menuPtr->titlePtr = NULL;
    menuPtr->errorStructPtr = NULL;
    menuPtr->optionTablesPtr = optionTablesPtr;
    TkMenuInitializeDrawingFields(menuPtr);

    Tk_SetClass(menuPtr->tkwin, "Menu");
    Tk_SetClassProcs(menuPtr->tkwin, &menuClass, (ClientData) menuPtr);
    if (Tk_InitOptions(interp, (char *) menuPtr,
          menuPtr->optionTablesPtr->menuOptionTable, menuPtr->tkwin)
          != TCL_OK) {
      Tk_DestroyWindow(menuPtr->tkwin);
      ckfree((char *) menuPtr);
      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);
      ckfree((char *) menuPtr);
      return TCL_ERROR;
    }

    Tk_CreateEventHandler(new, ExposureMask|StructureNotifyMask|ActivateMask,
          TkMenuEventProc, (ClientData) menuPtr);
    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;
      Tcl_Obj *newObjv[2];

        while (cascadeListPtr != NULL) {

          nextCascadePtr = cascadeListPtr->nextCascadePtr;
     
          /*
           * If we have a new master menu, and an existing cloned menu
           * points to this menu in a cascade entry, we have to clone
           * the new menu and point the entry to the clone instead
           * of the menu we are creating. Otherwise, ConfigureMenuEntry
           * will hook up the platform-specific cascade linkages now
           * that the menu we are creating exists.
           */
           
          if ((menuPtr->masterMenuPtr != menuPtr)
                || ((menuPtr->masterMenuPtr == menuPtr)
                && ((cascadeListPtr->menuPtr->masterMenuPtr
                == cascadeListPtr->menuPtr)))) {
            newObjv[0] = Tcl_NewStringObj("-menu", -1);
            newObjv[1] = Tcl_NewStringObj(Tk_PathName(menuPtr->tkwin), -1);
            Tcl_IncrRefCount(newObjv[0]);
            Tcl_IncrRefCount(newObjv[1]);
            ConfigureMenuEntry(cascadeListPtr, 2, newObjv);
            Tcl_DecrRefCount(newObjv[0]);
            Tcl_DecrRefCount(newObjv[1]);
          } else {
            Tcl_Obj *normalPtr = Tcl_NewStringObj("normal", -1);
            Tcl_Obj *windowNamePtr = Tcl_NewStringObj(
                  Tk_PathName(cascadeListPtr->menuPtr->tkwin), -1);

            Tcl_IncrRefCount(normalPtr);
            Tcl_IncrRefCount(windowNamePtr);
                  newMenuName = TkNewMenuName(menuPtr->interp,
                  windowNamePtr, menuPtr);
            Tcl_IncrRefCount(newMenuName);
                  CloneMenu(menuPtr, newMenuName, normalPtr);
                  
                /*
                 * Now we can set the new menu instance to be the cascade entry
                 * of the parent's instance.
                 */

            newObjv[0] = Tcl_NewStringObj("-menu", -1);
            newObjv[1] = newMenuName;
            Tcl_IncrRefCount(newObjv[0]);
                ConfigureMenuEntry(cascadeListPtr, 2, newObjv);
            Tcl_DecrRefCount(normalPtr);
            Tcl_DecrRefCount(newObjv[0]);
            Tcl_DecrRefCount(newObjv[1]);
            Tcl_DecrRefCount(windowNamePtr);
            }
            cascadeListPtr = nextCascadePtr;
        }
    }
    
    /*
     * If there already exist toplevel widgets that refer to this menu,
     * find them and notify them so that they can reconfigure their
     * geometry to reflect the menu.
     */
 
    if (menuRefPtr->topLevelListPtr != NULL) {
      TkMenuTopLevelList *topLevelListPtr = menuRefPtr->topLevelListPtr;
      TkMenuTopLevelList *nextPtr;
      Tk_Window listtkwin;
      while (topLevelListPtr != NULL) {
      
          /*
           * Need to get the next pointer first. TkSetWindowMenuBar
           * changes the list, so that the next pointer is different
           * after calling it.
           */
      
          nextPtr = topLevelListPtr->nextPtr;
          listtkwin = topLevelListPtr->tkwin;
          TkSetWindowMenuBar(menuPtr->interp, listtkwin, 
                Tk_PathName(menuPtr->tkwin), Tk_PathName(menuPtr->tkwin));
          topLevelListPtr = nextPtr;
      }
    }

    Tcl_SetResult(interp, Tk_PathName(menuPtr->tkwin), TCL_STATIC);
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * MenuWidgetObjCmd --
 *
 *    This procedure 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, interp, objc, objv)
    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 = (TkMenu *) clientData;
    register TkMenuEntry *mePtr;
    int result = TCL_OK;
    int option;

    if (objc < 2) {
      Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
      return TCL_ERROR;
    }
    if (Tcl_GetIndexFromObj(interp, objv[1], menuOptions, "option", 0,
          &option) != TCL_OK) {
      return TCL_ERROR;
    }
    Tcl_Preserve((ClientData) menuPtr);

    switch ((enum options) option) {
      case MENU_ACTIVATE: {
          int index;

          if (objc != 3) {
            Tcl_WrongNumArgs(interp, 1, objv, "activate 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, 1, objv, "add type ?options?");
            goto error;
          }

          if (MenuAddOrInsert(interp, menuPtr, (Tcl_Obj *) NULL,
                objc - 2, objv + 2) != TCL_OK) {
            goto error;
          }
          break;
      case MENU_CGET: {
          Tcl_Obj *resultPtr;

          if (objc != 3) {
            Tcl_WrongNumArgs(interp, 1, objv, "cget option");
            goto error;
          }
          resultPtr = Tk_GetOptionValue(interp, (char *) menuPtr,
                menuPtr->optionTablesPtr->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, 1, objv,
                  "clone 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,
                  menuPtr->optionTablesPtr->menuOptionTable,
                  (Tcl_Obj *) 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,
                  menuPtr->optionTablesPtr->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, 1, objv, "delete first ?last?");
            goto error;
          }
          if (TkGetMenuIndex(interp, menuPtr, objv[2], 0, &first) 
                != TCL_OK) {
            goto error;
          }
          if (objc == 3) {
            last = first;
          } else {
            if (TkGetMenuIndex(interp, menuPtr, objv[3], 0, &last) 
                  != TCL_OK) {
                goto error;
            }
          }
          if (menuPtr->tearoff && (first == 0)) {

            /*
             * Sorry, can't delete the tearoff entry;  must reconfigure
             * the menu.
             */
            
            first = 1;
          }
          if ((first < 0) || (last < first)) {
            goto done;
          }
          DeleteMenuCloneEntries(menuPtr, first, last);
          break;
      }
      case MENU_ENTRYCGET: {
          int index;
          Tcl_Obj *resultPtr;

          if (objc != 4) {
            Tcl_WrongNumArgs(interp, 1, objv, "entrycget 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((ClientData) mePtr);
          resultPtr = Tk_GetOptionValue(interp, (char *) mePtr, 
                mePtr->optionTable, objv[3], menuPtr->tkwin);
          Tcl_Release((ClientData) 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, 1, objv, 
                  "entryconfigure 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((ClientData) mePtr);
          if (objc == 3) {
            resultPtr = Tk_GetOptionInfo(interp, (char *) mePtr,
                  mePtr->optionTable, (Tcl_Obj *) 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((ClientData) mePtr);
          break;
      }
      case MENU_INDEX: {
          int index;

          if (objc != 3) {
            Tcl_WrongNumArgs(interp, 1, objv, "index string");
            goto error;
          }
          if (TkGetMenuIndex(interp, menuPtr, objv[2], 0, &index) 
                != TCL_OK) {
            goto error;
          }
          if (index < 0) {
            Tcl_SetResult(interp, "none", TCL_STATIC);
          } else {
            Tcl_SetIntObj(Tcl_GetObjResult(interp), index);
          }
          break;
      }
      case MENU_INSERT:
          if (objc < 4) {
            Tcl_WrongNumArgs(interp, 1, objv, 
                  "insert index type ?options?");
            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, 1, objv, "invoke index");
            goto error;
          }
          if (TkGetMenuIndex(interp, menuPtr, objv[2], 0, &index)
                != TCL_OK) {
            goto error;
          }
          if (index < 0) {
            goto done;
          }
          result = TkInvokeMenu(interp, menuPtr, index);
          break;
      }
      case MENU_POST: {
          int x, y;

          if (objc != 4) {
            Tcl_WrongNumArgs(interp, 1, objv, "post x y");
            goto error;
          }
          if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK)
                || (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) {
            goto error;
          }

          /*
           * Tearoff menus are posted differently on Mac and Windows than
           * non-tearoffs. TkpPostMenu does not actually map the menu's
           * window on those platforms, and popup menus have to be
           * handled specially.
           */
          
          if (menuPtr->menuType != TEAROFF_MENU) {
            result = TkpPostMenu(interp, menuPtr, x, y);
          } else {
            result = TkPostTearoffMenu(interp, menuPtr, x, y);
          }
          break;
      }
      case MENU_POSTCASCADE: {
          int index;

          if (objc != 3) {
            Tcl_WrongNumArgs(interp, 1, objv, "postcascade 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, (TkMenuEntry *) NULL);
          } else {
            result = TkPostSubmenu(interp, menuPtr, 
                  menuPtr->entries[index]);
          }
          break;
      }
      case MENU_TYPE: {
          int index;

          if (objc != 3) {
            Tcl_WrongNumArgs(interp, 1, objv, "type 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) {
            Tcl_SetResult(interp, "tearoff", TCL_STATIC);
          } else {
            Tcl_SetStringObj(Tcl_GetObjResult(interp),
                  menuEntryTypeStrings[menuPtr->entries[index]->type],
                  -1);
          }
          break;
      }
      case MENU_UNPOST:
          if (objc != 2) {
            Tcl_WrongNumArgs(interp, 1, objv, "unpost");
            goto error;
          }
          Tk_UnmapWindow(menuPtr->tkwin);
          result = TkPostSubmenu(interp, menuPtr, (TkMenuEntry *) NULL);
          break;
      case MENU_YPOSITION:
          if (objc != 3) {
            Tcl_WrongNumArgs(interp, 1, objv, "yposition index");
            goto error;
          }
          result = MenuDoYPosition(interp, menuPtr, objv[2]);
          break;
    }
    done:
    Tcl_Release((ClientData) menuPtr);
    return result;

    error:
    Tcl_Release((ClientData) 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(interp, menuPtr, index)
    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((ClientData) 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_Eval(interp, Tcl_DStringValue(&ds));
      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((ClientData) mePtr);
    done:
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyMenuInstance --
 *
 *    This procedure 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(menuPtr)
    TkMenu *menuPtr;    /* Info about menu widget. */
{
    int i;
    TkMenu *menuInstancePtr;
    TkMenuEntry *cascadePtr, *nextCascadePtr;
    Tcl_Obj *newObjv[2];
    TkMenu *parentMasterMenuPtr;
    TkMenuEntry *parentMasterEntryPtr;
    
    /*
     * If the menu has any cascade menu entries pointing to it, the cascade
     * entries need to be told that the menu is going away. We need to clear
     * the menu ptr field in the menu reference at this point in the code
     * so that everything else can forget about this menu properly. We also
     * need to reset -menu field of all entries that are not master menus
     * back to this entry name if this is a master menu pointed to by another
     * master menu. If there is a clone menu that points to this menu,
     * then this menu is itself a clone, so when this menu goes away,
     * the -menu field of the pointing entry must be set back to this
     * menu's master menu name so that later if another menu is created
     * the cascade hierarchy can be maintained.
     */

    TkpDestroyMenu(menuPtr);
    cascadePtr = menuPtr->menuRefPtr->parentEntryPtr;
    menuPtr->menuRefPtr->menuPtr = NULL;
    if (TkFreeMenuReferences(menuPtr->menuRefPtr)) {
      menuPtr->menuRefPtr = NULL;
    }

    for (; cascadePtr != NULL; cascadePtr = nextCascadePtr) {
      nextCascadePtr = cascadePtr->nextCascadePtr;
      
      if (menuPtr->masterMenuPtr != menuPtr) {
          Tcl_Obj *menuNamePtr = Tcl_NewStringObj("-menu", -1);

          parentMasterMenuPtr = cascadePtr->menuPtr->masterMenuPtr;
          parentMasterEntryPtr =
                parentMasterMenuPtr->entries[cascadePtr->index];
          newObjv[0] = menuNamePtr;
          newObjv[1] = parentMasterEntryPtr->namePtr;
          /*
           * It is possible that the menu info is out of sync, and
           * these things point to NULL, so verify existence [Bug: 3402]
           */
          if (newObjv[0] && newObjv[1]) {
            Tcl_IncrRefCount(newObjv[0]);
            Tcl_IncrRefCount(newObjv[1]);
            ConfigureMenuEntry(cascadePtr, 2, newObjv);
            Tcl_DecrRefCount(newObjv[0]);
            Tcl_DecrRefCount(newObjv[1]);
          }
      } else {
          ConfigureMenuEntry(cascadePtr, 0, (Tcl_Obj **) 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) {
       panic("Attempting to delete master menu when there are still clones.");
   }

    /*
     * Free up all the stuff that requires special handling, then
     * let Tk_FreeConfigOptions handle all the standard option-related
     * stuff.
     */

    for (i = menuPtr->numEntries; --i >= 0; ) {
      /*
       * As each menu entry is deleted from the end of the array of
       * entries, decrement menuPtr->numEntries.  Otherwise, the act of
       * deleting menu entry i will dereference freed memory attempting
       * to queue a redraw for menu entries (i+1)...numEntries.
       */
       
      DestroyMenuEntry((char *) menuPtr->entries[i]);
      menuPtr->numEntries = i;
    }
    if (menuPtr->entries != NULL) {
      ckfree((char *) menuPtr->entries);
    }
    TkMenuFreeDrawOptions(menuPtr);
    Tk_FreeConfigOptions((char *) menuPtr, 
          menuPtr->optionTablesPtr->menuOptionTable, menuPtr->tkwin);
    if (menuPtr->tkwin != NULL) {
      Tk_Window tkwin = menuPtr->tkwin;
      menuPtr->tkwin = NULL;
      Tk_DestroyWindow(tkwin);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkDestroyMenu --
 *
 *    This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
 *    to clean up the internal structure of a menu at a safe time
 *    (when no-one is using it anymore).  If called on a master instance,
 *    destroys all of the slave instances. If called on a non-master
 *    instance, just destroys that instance.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Everything associated with the menu is freed up.
 *
 *----------------------------------------------------------------------
 */

void
TkDestroyMenu(menuPtr)
    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(mePtr)
    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 procedure 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(memPtr)
    char *memPtr;       /* Pointer to entry to be freed. */
{
    register TkMenuEntry *mePtr = (TkMenuEntry *) 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, (TkMenuEntry *) 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;
          /* 
           * The menu as a whole is a clone.  We must delete the clone
           * of the cascaded menu for the particular entry we are
           * destroying.
           */
          TkMenuReferences *menuRefPtr = mePtr->childMenuRefPtr;
          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);
          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)) {
      char *varName = Tcl_GetStringFromObj(mePtr->namePtr, NULL);
      Tcl_UntraceVar(menuPtr->interp, varName,
            TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
            MenuVarProc, (ClientData) mePtr);
    }
    TkpDestroyMenuEntry(mePtr);
    TkMenuEntryFreeDrawOptions(mePtr);
    Tk_FreeConfigOptions((char *) mePtr, mePtr->optionTable, menuPtr->tkwin);
    ckfree((char *) mePtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * MenuWorldChanged --
 *
 *      This procedure 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(instanceData)
    ClientData instanceData;  /* Information about widget. */
{
    TkMenu *menuPtr = (TkMenu *) instanceData;
    int i;
    
    TkMenuConfigureDrawOptions(menuPtr);
    for (i = 0; i < menuPtr->numEntries; i++) {
      TkMenuConfigureEntryDrawOptions(menuPtr->entries[i],
            menuPtr->entries[i]->index);
      TkpConfigureMenuEntry(menuPtr->entries[i]);     
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureMenu --
 *
 *    This procedure 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(interp, menuPtr, objc, objv)
    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;
    
    for (menuListPtr = menuPtr->masterMenuPtr; menuListPtr != NULL;
          menuListPtr = menuListPtr->nextInstancePtr) {
      menuListPtr->errorStructPtr = (Tk_SavedOptions *)
            ckalloc(sizeof(Tk_SavedOptions));
      result = Tk_SetOptions(interp, (char *) menuListPtr,
            menuListPtr->optionTablesPtr->menuOptionTable, objc, objv, 
            menuListPtr->tkwin, menuListPtr->errorStructPtr, (int *) NULL);
      if (result != TCL_OK) {
          for (cleanupPtr = menuPtr->masterMenuPtr;
                cleanupPtr != menuListPtr;
                cleanupPtr = cleanupPtr->nextInstancePtr) {
            Tk_RestoreSavedOptions(cleanupPtr->errorStructPtr);
            ckfree((char *) cleanupPtr->errorStructPtr);
            cleanupPtr->errorStructPtr = NULL;
          }
          if (menuListPtr->errorStructPtr != NULL) {
            Tk_RestoreSavedOptions(menuListPtr->errorStructPtr);
            ckfree((char *) 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_GetIndexFromObj(NULL, menuListPtr->menuTypePtr,
                menuTypeStrings, NULL, 0, &menuListPtr->menuType);

          /*
           * Configure the new window to be either a pop-up menu
           * or a tear-off menu.
           * We don't do this for menubars since they are not toplevel
           * windows. Also, since this gets called before CloneMenu has
           * a chance to set the menuType field, we have to look at the
           * menuTypeName field to tell that this is a menu bar.
           */
          
          if (menuListPtr->menuType == MASTER_MENU) {
            TkpMakeMenuWindow(menuListPtr->tkwin, 1);
          } else if (menuListPtr->menuType == TEAROFF_MENU) {
            TkpMakeMenuWindow(menuListPtr->tkwin, 0);
          }
      }


      /*
       * 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((char *) cleanupPtr->errorStructPtr);
                  cleanupPtr->errorStructPtr = NULL;
                }
                if (menuListPtr->errorStructPtr != NULL) {
                  Tk_RestoreSavedOptions(menuListPtr->errorStructPtr);
                  ckfree((char *) menuListPtr->errorStructPtr);
                  menuListPtr->errorStructPtr = NULL;
                }
                return TCL_ERROR;
            }
          }
      } else if ((menuListPtr->numEntries > 0)
            && (menuListPtr->entries[0]->type == TEAROFF_ENTRY)) {
          int i;
          
          Tcl_EventuallyFree((ClientData) menuListPtr->entries[0],
                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((char *) 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, (Tcl_Obj **) NULL);
      }
      
      TkEventuallyRecomputeMenu(menuListPtr);
    }

    for (cleanupPtr = menuPtr->masterMenuPtr; cleanupPtr != NULL;
          cleanupPtr = cleanupPtr->nextInstancePtr) {
      Tk_FreeSavedOptions(cleanupPtr->errorStructPtr);
      ckfree((char *) 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(mePtr)
    TkMenuEntry *mePtr;             /* The entry we are configuring. */
{
    TkMenu *menuPtr = mePtr->menuPtr;
    int index = mePtr->index;
    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_GetStringFromObj(mePtr->namePtr, NULL);
      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;
    }

    if (TkpConfigureMenuEntry(mePtr) != 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) {
      char *imageString = Tcl_GetStringFromObj(mePtr->imagePtr, NULL);
      image = Tk_GetImage(menuPtr->interp, menuPtr->tkwin, imageString,
            TkMenuImageProc, (ClientData) 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) {
      char *selectImageString = Tcl_GetStringFromObj(
            mePtr->selectImagePtr, NULL);
      image = Tk_GetImage(menuPtr->interp, menuPtr->tkwin, selectImageString,
            TkMenuSelectImageProc, (ClientData) 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;
      char *name;

      if (mePtr->namePtr == NULL) {
          if (mePtr->labelPtr == NULL) {
            mePtr->namePtr = NULL;
          } else {
            mePtr->namePtr = Tcl_DuplicateObj(mePtr->labelPtr);
            Tcl_IncrRefCount(mePtr->namePtr);
          }
      }
      if (mePtr->onValuePtr == NULL) {
          if (mePtr->labelPtr == NULL) {
            mePtr->onValuePtr = NULL;
          } else {
            mePtr->onValuePtr = Tcl_DuplicateObj(mePtr->labelPtr);
            Tcl_IncrRefCount(mePtr->onValuePtr);
          }
      }

      /*
       * Select the entry if the associated variable has the
       * appropriate value, initialize the variable if it doesn't
       * exist, then set a trace on the variable to monitor future
       * changes to its value.
       */
      
      if (mePtr->namePtr != NULL) {
          valuePtr = Tcl_ObjGetVar2(menuPtr->interp, mePtr->namePtr, NULL,
                TCL_GLOBAL_ONLY);
      } else {
          valuePtr = NULL;
      }
      mePtr->entryFlags &= ~ENTRY_SELECTED;
      if (valuePtr != NULL) {
          if (mePtr->onValuePtr != NULL) {
            char *value = Tcl_GetStringFromObj(valuePtr, NULL);
            char *onValue = Tcl_GetStringFromObj(mePtr->onValuePtr,
                  NULL);


            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_GetStringFromObj(mePtr->namePtr, NULL);
          Tcl_TraceVar(menuPtr->interp, name,
                TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
                MenuVarProc, (ClientData) mePtr);
      }
    }
    
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureMenuEntry --
 *
 *    This procedure 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(mePtr, objc, objv)
    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 procedure.
     */

    if ((mePtr->namePtr != NULL)
          && ((mePtr->type == CHECK_BUTTON_ENTRY)
          || (mePtr->type == RADIO_BUTTON_ENTRY))) {
      char *name = Tcl_GetStringFromObj(mePtr->namePtr, NULL);
      Tcl_UntraceVar(menuPtr->interp, name,
            TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
            MenuVarProc, (ClientData) mePtr);
    }

    result = TCL_OK;
    if (menuPtr->tkwin != NULL) {
      if (Tk_SetOptions(menuPtr->interp, (char *) mePtr,
            mePtr->optionTable, objc, objv, menuPtr->tkwin,
            &errorStruct, (int *) 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(interp, menuPtr, index, objc, objv)
    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;
    char *newCascadeName;

    /*
     * Cascades are kind of tricky here. This is special case #3 in the comment
     * at the top of this file. Basically, if a menu is the master menu of a
     * clone chain, and has an entry with a cascade menu, the clones of
     * the menu will point to clones of the cascade menu. We have
     * to destroy the clones of the cascades, clone the new cascade
     * menu, and configure the entry to point to the new clone.
     */

    mePtr = menuPtr->masterMenuPtr->entries[index];
    if (mePtr->type == CASCADE_ENTRY) {
      oldCascadePtr = mePtr->namePtr;
      if (oldCascadePtr != NULL) {
          Tcl_IncrRefCount(oldCascadePtr);
      }
    }

    if (ConfigureMenuEntry(mePtr, objc, objv) != TCL_OK) {
      return TCL_ERROR;
    }

    if (mePtr->type == CASCADE_ENTRY) {
      char *oldCascadeName;

      if (mePtr->namePtr != NULL) {
          newCascadeName = Tcl_GetStringFromObj(mePtr->namePtr, NULL);
      } 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_GetStringFromObj(oldCascadePtr,
                NULL);
          cascadeEntryChanged = (strcmp(oldCascadeName, newCascadeName) 
                != 0);
      }
      if (oldCascadePtr != NULL) {
          Tcl_DecrRefCount(oldCascadePtr);
      }
    }

    if (cascadeEntryChanged) {
      if (mePtr->namePtr != NULL) {
          newCascadeName = Tcl_GetStringFromObj(mePtr->namePtr, NULL);
          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->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(interp, menuPtr, objPtr, lastOK, indexPtr)
    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;
    char *string = Tcl_GetStringFromObj(objPtr, NULL);

    if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
      *indexPtr = menuPtr->active;
      goto success;
    }

    if (((string[0] == 'l') && (strcmp(string, "last") == 0))
          || ((string[0] == 'e') && (strcmp(string, "end") == 0))) {
      *indexPtr = menuPtr->numEntries - ((lastOK) ? 0 : 1);
      goto success;
    }

    if ((string[0] == 'n') && (strcmp(string, "none") == 0)) {
      *indexPtr = -1;
      goto success;
    }

    if (string[0] == '@') {
      if (GetIndexFromCoords(interp, menuPtr, string, indexPtr)
            == TCL_OK) {
          goto success;
      }
    }

    if (isdigit(UCHAR(string[0]))) {
      if (Tcl_GetInt(interp, string,  &i) == TCL_OK) {
          if (i >= menuPtr->numEntries) {
            if (lastOK) {
                i = menuPtr->numEntries;
            } else {
                i = menuPtr->numEntries-1;
            }
          } else if (i < 0) {
            i = -1;
          }
          *indexPtr = i;
          goto success;
      }
      Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
    }

    for (i = 0; i < menuPtr->numEntries; i++) {
      Tcl_Obj *labelPtr = menuPtr->entries[i]->labelPtr;
      char *label = (labelPtr == NULL) ? NULL
              : Tcl_GetStringFromObj(labelPtr, NULL);
      
      if ((label != NULL)
            && (Tcl_StringMatch(label, string))) {
          *indexPtr = i;
          goto success;
      }
    }

    Tcl_AppendResult(interp, "bad menu entry index \"",
          string, "\"", (char *) NULL);
    return TCL_ERROR;

success:
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * MenuCmdDeletedProc --
 *
 *    This procedure 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 clientData;    /* Pointer to widget record for widget. */
{
    TkMenu *menuPtr = (TkMenu *) clientData;
    Tk_Window tkwin = menuPtr->tkwin;

    /*
     * This procedure 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 procedure
     * 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 procedure 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(menuPtr, index, type)
    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;

    /*
     * Create a new array of entries with an empty slot for the
     * new entry.
     */

    newEntries = (TkMenuEntry **) ckalloc((unsigned)
          ((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((char *) menuPtr->entries);
    }
    menuPtr->entries = newEntries;
    menuPtr->numEntries++;
    mePtr = (TkMenuEntry *) ckalloc(sizeof(TkMenuEntry));
    menuPtr->entries[index] = mePtr;
    mePtr->type = type;
    mePtr->optionTable = menuPtr->optionTablesPtr->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((char *) mePtr);
      return NULL;
    }
    TkMenuInitializeEntryDrawingFields(mePtr);
    if (TkpMenuNewEntry(mePtr) != TCL_OK) {
      Tk_FreeConfigOptions((char *) mePtr, mePtr->optionTable,
            menuPtr->tkwin);
      ckfree((char *) mePtr);
      return NULL;
    }

    return mePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * MenuAddOrInsert --
 *
 *    This procedure 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(interp, menuPtr, indexPtr, objc, objv)
    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) {
      char *indexString = Tcl_GetStringFromObj(indexPtr, NULL);
      Tcl_AppendResult(interp, "bad index \"", indexString, "\"",
             (char *) NULL);
      return TCL_ERROR;
    }
    if (menuPtr->tearoff && (index == 0)) {
      index = 1;
    }

    /*
     * Figure out the type of the new entry.
     */

    if (Tcl_GetIndexFromObj(interp, objv[0], menuEntryTypeStrings,
          "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((ClientData) errorMenuPtr->entries[index],
                  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((char *) errorMenuPtr->entries);
                errorMenuPtr->entries = NULL;
            }
            if (errorMenuPtr == menuListPtr) {
                break;
            }
          }
          return TCL_ERROR;
      }
      
      /*
       * If a menu has cascades, then every instance of the menu has
       * to have its own parallel cascade structure. So adding an
       * entry to a menu with clones means that the menu that the
       * entry points to has to be cloned for every clone the
       * master menu has. This is special case #2 in the comment
       * at the top of this file.
       */
 
      if ((menuPtr != menuListPtr) && (type == CASCADE_ENTRY)) {            
          if ((mePtr->namePtr != NULL)
                && (mePtr->childMenuRefPtr != NULL)
                && (mePtr->childMenuRefPtr->menuPtr != NULL)) {
              TkMenu *cascadeMenuPtr =
                  mePtr->childMenuRefPtr->menuPtr->masterMenuPtr;
              Tcl_Obj *newCascadePtr;
            Tcl_Obj *menuNamePtr = Tcl_NewStringObj("-menu", -1);
            Tcl_Obj *windowNamePtr = 
                  Tcl_NewStringObj(Tk_PathName(menuListPtr->tkwin), -1);
            Tcl_Obj *normalPtr = Tcl_NewStringObj("normal", -1);
            Tcl_Obj *newObjv[2];
            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) {
                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 procedure 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, interp, name1, name2, flags)
    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 = (TkMenuEntry *) clientData;
    TkMenu *menuPtr;
    CONST char *value;
    char *name;
    char *onValue;

    if (flags & TCL_INTERP_DESTROYED) {
      /*
       * Do nothing if the interpreter is going away.
       */

      return (char *) NULL;
    }

    menuPtr = mePtr->menuPtr;
    name = Tcl_GetStringFromObj(mePtr->namePtr, NULL);

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

    if (flags & TCL_TRACE_UNSETS) {
      mePtr->entryFlags &= ~ENTRY_SELECTED;
      if (flags & TCL_TRACE_DESTROYED) {
          Tcl_TraceVar(interp, name,
                TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
                MenuVarProc, clientData);
      }
      TkpConfigureMenuEntry(mePtr);
      TkEventuallyRedrawMenu(menuPtr, (TkMenuEntry *) NULL);
      return (char *) NULL;
    }

    /*
     * Use the value of the variable to update the selected status of
     * the menu entry.
     */

    value = Tcl_GetVar(interp, name, TCL_GLOBAL_ONLY);
    if (value == NULL) {
      value = "";
    }
    if (mePtr->onValuePtr != NULL) {
      onValue = Tcl_GetStringFromObj(mePtr->onValuePtr, NULL);
      if (strcmp(value, onValue) == 0) {
          if (mePtr->entryFlags & ENTRY_SELECTED) {
            return (char *) NULL;
          }
          mePtr->entryFlags |= ENTRY_SELECTED;
      } else if (mePtr->entryFlags & ENTRY_SELECTED) {
          mePtr->entryFlags &= ~ENTRY_SELECTED;
      } else {
          return (char *) NULL;
      }
    } else {
      return (char *) NULL;
    }
    TkpConfigureMenuEntry(mePtr);
    TkEventuallyRedrawMenu(menuPtr, mePtr);
    return (char *) NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * TkActivateMenuEntry --
 *
 *    This procedure 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(menuPtr, index)
    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(menuPtr)
    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(menuPtr, newMenuNamePtr, newMenuTypePtr)
    TkMenu *menuPtr;          /* The menu we are going to clone */
    Tcl_Obj *newMenuNamePtr;  /* The name to give the new menu */
    Tcl_Obj *newMenuTypePtr;  /* What kind of menu is this, a normal menu
                         * a menubar, or a tearoff? */
{
    int returnResult;
    int menuType, i;
    TkMenuReferences *menuRefPtr;
    Tcl_Obj *menuDupCommandArray[4];
    
    if (newMenuTypePtr == NULL) {
      menuType = MASTER_MENU;
    } else {
      if (Tcl_GetIndexFromObj(menuPtr->interp, newMenuTypePtr, 
            menuTypeStrings, "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((ClientData) 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)) != (TkMenuReferences *) NULL)
          && (menuPtr->numEntries == menuRefPtr->menuPtr->numEntries)) {
      TkMenu *newMenuPtr = menuRefPtr->menuPtr;
      Tcl_Obj *newObjv[3];
      int i, numElements;

      /*
       * Now put this newly created menu into the parent menu's instance
       * chain.
       */

      if (menuPtr->nextInstancePtr == NULL) {
          menuPtr->nextInstancePtr = newMenuPtr;
          newMenuPtr->masterMenuPtr = menuPtr->masterMenuPtr;
      } else {
          TkMenu *masterMenuPtr;
          
          masterMenuPtr = menuPtr->masterMenuPtr;
          newMenuPtr->nextInstancePtr = masterMenuPtr->nextInstancePtr;
          masterMenuPtr->nextInstancePtr = newMenuPtr;
          newMenuPtr->masterMenuPtr = masterMenuPtr;
      }
      
      /*
       * Add the master menu's window to the bind tags for this window
       * after this window's tag. This is so the user can bind to either
       * this clone (which may not be easy to do) or the entire menu
       * clone structure.
       */
      
      newObjv[0] = Tcl_NewStringObj("bindtags", -1);
      newObjv[1] = Tcl_NewStringObj(Tk_PathName(newMenuPtr->tkwin), -1);
      Tcl_IncrRefCount(newObjv[0]);
      Tcl_IncrRefCount(newObjv[1]);
      if (Tk_BindtagsObjCmd((ClientData)newMenuPtr->tkwin, 
            newMenuPtr->interp, 2, newObjv) == TCL_OK) {
          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_GetStringFromObj(elementPtr, NULL);
            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((ClientData)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((ClientData) menuPtr);
    return returnResult;
}

/*
 *----------------------------------------------------------------------
 *
 * 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(interp, menuPtr, objPtr)
    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 "@int", return the menu item corresponding
 *    to int.
 *
 * Results:
 *    If int is a valid number, *indexPtr will be the number of the menuentry
 *    that is the correct height. If int is invaled, *indexPtr will be
 *    unchanged. Returns appropriate Tcl error number.
 *
 * Side effects:
 *    If int is invalid, interp's result will set to NULL.
 *
 *----------------------------------------------------------------------
 */

static int
GetIndexFromCoords(interp, menuPtr, string, indexPtr)
    Tcl_Interp *interp;       /* interp of menu */
    TkMenu *menuPtr;          /* the menu we are searching */
    char *string;       /* The @string we are parsing */
    int *indexPtr;            /* The index of the item that matches */
{
    int x, y, i;
    char *p, *end;
    
    TkRecomputeMenu(menuPtr);
    p = string + 1;
    y = strtol(p, &end, 0);
    if (end == p) {
      goto error;
    }
    if (*end == ',') {
      x = y;
      p = end + 1;
      y = strtol(p, &end, 0);
      if (end == p) {
          goto error;
      }
    } else {
      Tk_GetPixelsFromObj(interp, menuPtr->tkwin, 
            menuPtr->borderWidthPtr, &x);
    }
    
    for (i = 0; i < menuPtr->numEntries; i++) {
      if ((x >= menuPtr->entries[i]->x) && (y >= menuPtr->entries[i]->y)
            && (x < (menuPtr->entries[i]->x + menuPtr->entries[i]->width))
            && (y < (menuPtr->entries[i]->y
            + menuPtr->entries[i]->height))) {
          break;
      }
    }
    if (i >= menuPtr->numEntries) {
      /* i = menuPtr->numEntries - 1; */
      i = -1;
    }
    *indexPtr = i;
    return TCL_OK;

    error:
    Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
    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(menuPtr)
    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(interp, parentPtr, menuPtr)
    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_CmdInfo cmdInfo;
    Tcl_HashTable *nameTablePtr = NULL;
    TkWindow *winPtr = (TkWindow *) menuPtr->tkwin;
    char *parentName = Tcl_GetStringFromObj(parentPtr, NULL);

    if (winPtr->mainPtr != NULL) {
      nameTablePtr = &(winPtr->mainPtr->nameTable);
    }

    doDot = parentName[strlen(parentName) - 1] != '.';

    childPtr = Tcl_NewStringObj(Tk_PathName(menuPtr->tkwin), -1);
    for (destString = Tcl_GetStringFromObj(childPtr, NULL);
          *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_GetStringFromObj(resultPtr, NULL);
      if ((Tcl_GetCommandInfo(interp, destString, &cmdInfo) == 0)
            && ((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(interp, tkwin, oldMenuName, menuName)
    Tcl_Interp *interp;       /* The interpreter the toplevel lives in. */
    Tk_Window tkwin;          /* The toplevel window */
    char *oldMenuName;        /* The name of the menubar previously set in
                         * this toplevel. NULL means no menu was
                         * set previously. */
    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;
    
    TkMenuInit();

    /*
     * 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((char *) 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 = (TkMenuTopLevelList *)
            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, interp)
    ClientData clientData;    /* The menu hash table we are destroying */
    Tcl_Interp *interp;       /* The interpreter we are destroying */
{
    Tcl_DeleteHashTable((Tcl_HashTable *) clientData);
    ckfree((char *) 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(interp)
    Tcl_Interp *interp;       /* The interp we need the hash table in.*/
{
    Tcl_HashTable *menuTablePtr;

    menuTablePtr = (Tcl_HashTable *) Tcl_GetAssocData(interp, MENU_HASH_KEY,
          NULL);
    if (menuTablePtr == NULL) {
      menuTablePtr = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
      Tcl_InitHashTable(menuTablePtr, TCL_STRING_KEYS);
      Tcl_SetAssocData(interp, MENU_HASH_KEY, DestroyMenuHashTable,
            (ClientData) 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(interp, pathName)
    Tcl_Interp *interp;
    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 = (TkMenuReferences *) ckalloc(sizeof(TkMenuReferences));
      menuRefPtr->menuPtr = NULL;
      menuRefPtr->topLevelListPtr = NULL;
      menuRefPtr->parentEntryPtr = NULL;
      menuRefPtr->hashEntryPtr = hashEntryPtr;
      Tcl_SetHashValue(hashEntryPtr, (char *) menuRefPtr);
    } else {
      menuRefPtr = (TkMenuReferences *) 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(interp, pathName)
    Tcl_Interp *interp;       /* The interp the menu is living in. */
    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 = (TkMenuReferences *) 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(interp, objPtr)
    Tcl_Interp *interp;       /* The interp the menu is living in. */
    Tcl_Obj *objPtr;          /* The path of the menu widget */
{
    char *pathName = Tcl_GetStringFromObj(objPtr, NULL);
    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(menuRefPtr)
    TkMenuReferences *menuRefPtr;         /* The menu reference to
                                     * free */
{
    if ((menuRefPtr->menuPtr == NULL) 
          && (menuRefPtr->parentEntryPtr == NULL)
          && (menuRefPtr->topLevelListPtr == NULL)) {
      Tcl_DeleteHashEntry(menuRefPtr->hashEntryPtr);
      ckfree((char *) 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(menuPtr, first, last)
    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;

    numDeleted = last + 1 - first;
    for (menuListPtr = menuPtr->masterMenuPtr; menuListPtr != NULL;
          menuListPtr = menuListPtr->nextInstancePtr) {
      for (i = last; i >= first; i--) {
          Tcl_EventuallyFree((ClientData) menuListPtr->entries[i],
                DestroyMenuEntry);
      }
      for (i = last + 1; i < menuListPtr->numEntries; i++) {
          menuListPtr->entries[i - numDeleted] = menuListPtr->entries[i];
          menuListPtr->entries[i - numDeleted]->index = i - numDeleted;
      }
      menuListPtr->numEntries -= numDeleted;
      if (menuListPtr->numEntries == 0) {
          ckfree((char *) 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()
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 
          Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    
    if (!menusInitialized) {
      Tcl_MutexLock(&menuMutex);
      if (!menusInitialized) {
          TkpMenuInit();
          menusInitialized = 1;
      }
      /* 
       * Make sure we cleanup on finalize. 
       */ 
      Tcl_CreateExitHandler((Tcl_ExitProc *) TkMenuCleanup, NULL); 
      Tcl_MutexUnlock(&menuMutex);
    }
    if (!tsdPtr->menusInitialized) {
      TkpMenuThreadInit();
      tsdPtr->menusInitialized = 1;
    }
}

Generated by  Doxygen 1.6.0   Back to index