/*
 * tkMacOSXMenu.c --
 *
 *	This module implements the Mac-platform specific features of menus.
 *
 * Copyright (c) 1996-1997 by Sun Microsystems, Inc.
 * Copyright 2001, Apple Computer, Inc.
 * Copyright (c) 2005-2007 Daniel A. Steffen <das@users.sourceforge.net>
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tkMacOSXPrivate.h"
#include "tkMenubutton.h"
#include "tkMenu.h"
#include "tkColor.h"
#include "tkFont.h"
#include "tkMacOSXDebug.h"

/*
#ifdef TK_MAC_DEBUG
#define TK_MAC_DEBUG_MENUS
#endif
*/

#define USE_TK_MDEF

typedef struct MacMenu {
    MenuRef menuHdl;		/* The Menu Manager data structure. */
#ifdef USE_TK_MDEF
    int useMDEF;		/* true if this menu uses the MDEF */
#endif
} MacMenu;

typedef struct MenuEntryUserData {
    Drawable mdefDrawable;
    TkMenuEntry *mePtr;
    Tk_Font tkfont;
    Tk_FontMetrics *fmPtr;
} MenuEntryUserData;

/*
 * Platform specific flags for menu entries
 *
 * ENTRY_COMMAND_ACCEL		Indicates the entry has the command key
 *				in its accelerator string.
 * ENTRY_OPTION_ACCEL		Indicates the entry has the option key
 *				in its accelerator string.
 * ENTRY_SHIFT_ACCEL		Indicates the entry has the shift key
 *				in its accelerator string.
 * ENTRY_CONTROL_ACCEL		Indicates the entry has the control key
 *				in its accelerator string.
 */

#define ENTRY_COMMAND_ACCEL	ENTRY_PLATFORM_FLAG1
#define ENTRY_OPTION_ACCEL	ENTRY_PLATFORM_FLAG2
#define ENTRY_SHIFT_ACCEL	ENTRY_PLATFORM_FLAG3
#define ENTRY_CONTROL_ACCEL	ENTRY_PLATFORM_FLAG4
#define ENTRY_ACCEL_MASK	(ENTRY_COMMAND_ACCEL | ENTRY_OPTION_ACCEL \
				| ENTRY_SHIFT_ACCEL | ENTRY_CONTROL_ACCEL)
#define MODIFIER_NUM 4

/*
 * This structure is used to keep track of subfields within Macintosh menu
 * items.
 */

typedef struct EntryGeometry {
    int accelTextStart;		/* Offset into the accel string where
				 * the text starts. Everything before
				 * this is modifier key descriptions.
				 */
    int modifierWidth;		/* Width of modifier symbols. */
    int accelTextWidth;		/* Width of the text after the modifier
				 * keys. */
    int nonAccelMargin;		/* The width of the margin for entries
				 * without accelerators. */
    int modifierNum;		/* Number of modifiers */
    Tcl_UniChar modifierUniChars[MODIFIER_NUM];
				/* Modifiers in unicode */
    char accelGlyph;		/* Accelerator glyph, if any */
} EntryGeometry;

/*
 * Structure to keep track of toplevel windows and their menubars.
 */

typedef struct TopLevelMenubarList {
    struct TopLevelMenubarList *nextPtr;
				/* The next window in the list. */
    Tk_Window tkwin;		/* The toplevel window. */
    TkMenu *menuPtr;		/* The menu associated with this
				 * toplevel. */
} TopLevelMenubarList;

/*
 * Platform-specific flags for menus.
 *
 * MENU_APPLE_MENU		0 indicates a custom Apple menu has
 *				not been installed; 1 a custom Apple
 *				menu has been installed.
 * MENU_HELP_MENU		0 indicates a custom Help menu has
 *				not been installed; 1 a custom Help
 *				menu has been installed.
 * MENU_RECONFIGURE_PENDING	1 indicates that an idle handler has
 *				been scheduled to reconfigure the
 *				Macintosh MenuHandle.
 */

#define MENU_APPLE_MENU			MENU_PLATFORM_FLAG1
#define MENU_HELP_MENU			MENU_PLATFORM_FLAG2
#define MENU_RECONFIGURE_PENDING	MENU_PLATFORM_FLAG3

#define CASCADE_CMD (0x1b)	/* The special command char for cascade
				 * menus. */
#define MENUBAR_REDRAW_PENDING 1

static int gNoTkMenus = 0;	/* This is used by Tk_MacOSXTurnOffMenus as the
				 * flag that Tk is not to draw any menus. */

static Tcl_HashTable commandTable;
				/* The list of menuInstancePtrs associated with
				 * menu ids */
static short currentAppleMenuID;
				/* The id of the current Apple menu. 0 for
				 * none. */
static short currentHelpMenuID; /* The id of the current Help menu. 0 for
				 * none. */
static Tcl_Interp *currentMenuBarInterp;
				/* The interpreter of the window that owns
				 * the current menubar. */
static char *currentMenuBarName;
				/* Malloced. Name of current menu in menu bar.
				 * NULL if no menu set. TO DO: make this a
				 * DString. */
static Tk_Window currentMenuBarOwner;
				/* Which window owns the current menu bar. */
static int inPostMenu;		/* We cannot be re-entrant like X
				 * windows. */
static short lastMenuID;	/* To pass to NewMenu; need to figure out
				 * a good way to do this. */
static short lastCascadeID;
				/* Cascades have to have ids that are
				 * less than 256. */
static int menuBarFlags;	/* Used for whether the menu bar needs
				 * redrawing or not. */

struct MenuCommandHandlerData { /* This is the ClientData we pass to */
    TkMenu *menuPtr;		/* Tcl_DoWhenIdle to move handling */
    int index;			/* menu commands to the event loop. */
};

static TopLevelMenubarList *windowListPtr;
				/* A list of windows that have menubars set. */

/*
 * Array of unicode, charcode and utf representations of the most common
 * special menu symbols.
 */
typedef struct MenuSymbol {
    const Tcl_UniChar unicode;
    const char charCode;
    /* char padding; */
    int utfLen, width;
    char utf[TCL_UTF_MAX + 1];
} MenuSymbol;

static MenuSymbol menuSymbols[] = {
    {kCommandUnicode,	kCommandCharCode},
    {kOptionUnicode,	kMenuOptionGlyph},
    {kControlUnicode,	kMenuControlGlyph},
    {kShiftUnicode,	kMenuShiftGlyph},
    {kCheckUnicode,	kCheckCharCode},
    {kDiamondUnicode,	kDiamondCharCode},
    {kBulletUnicode,	kBulletCharCode},
    {0x2026,		kNullCharCode},
    {0,			0},
};

enum MenuSymbolIdx {
    COMMAND_SYMBOL,
    OPTION_SYMBOL,
    CONTROL_SYMBOL,
    SHIFT_SYMBOL,
    CHECK_SYMBOL,
    DIAMDOND_SYMBOL,
    BULLET_SYMBOL,
    ELLIPSIS_SYMBOL,
};

MenuRef tkCurrentAppleMenu = NULL;

static SInt32 menuMarkColumnWidth = 0, menuMarkIndent = 0;
static SInt32 menuTextLeadingEdgeMargin = 0, menuTextTrailingEdgeMargin = 0;
static SInt16 menuItemExtraHeight = 0, menuItemExtraWidth = 0;
static SInt16 menuSeparatorHeight = 0;

/*
 * Forward declarations for procedures defined later in this file:
 */

MODULE_SCOPE int TkMacOSXGetNewMenuID(Tcl_Interp *interp, TkMenu *menuInstPtr,
	int cascade, short *menuIDPtr);
MODULE_SCOPE void TkMacOSXFreeMenuID(short menuID);

static void CompleteIdlers(TkMenu *menuPtr);
static void DrawMenuBarWhenIdle(ClientData clientData);
static void DrawMenuEntryAccelerator(TkMenu *menuPtr, TkMenuEntry *mePtr,
	Drawable d, GC gc, Tk_Font tkfont, const Tk_FontMetrics *fmPtr,
	Tk_3DBorder activeBorder, int x, int y, int width, int height,
	int drawArrow);
static void DrawMenuEntryBackground(TkMenu *menuPtr, TkMenuEntry *mePtr,
	Drawable d, Tk_3DBorder activeBorder, Tk_3DBorder bgBorder, int x,
	int y, int width, int heigth);
static void DrawMenuEntryIndicator(TkMenu *menuPtr, TkMenuEntry *mePtr,
	Drawable d, GC gc, GC indicatorGC, Tk_Font tkfont,
	const Tk_FontMetrics *fmPtr, int x, int y, int width, int height);
static void DrawMenuEntryLabel(TkMenu * menuPtr, TkMenuEntry *mePtr,
	Drawable d, GC gc, Tk_Font tkfont, const Tk_FontMetrics *fmPtr, int x,
	int y, int width, int height);
static void DrawMenuSeparator(TkMenu *menuPtr, TkMenuEntry *mePtr, Drawable d,
	GC gc, Tk_Font tkfont, const Tk_FontMetrics *fmPtr, int x, int y,
	int width, int height);
static void DrawTearoffEntry(TkMenu *menuPtr, TkMenuEntry *mePtr, Drawable d,
	GC gc, Tk_Font tkfont, const Tk_FontMetrics *fmPtr, int x, int y,
	int width, int height);
static void EventuallyInvokeMenu(ClientData data);
static void GetEntryText(TkMenuEntry *mePtr, Tcl_DString *dStringPtr);
static void GetMenuAccelGeometry(TkMenu *menuPtr, TkMenuEntry *mePtr,
	Tk_Font tkfont, const Tk_FontMetrics *fmPtr, int *modWidthPtr,
	int *textWidthPtr, int *heightPtr);
static void GetMenuLabelGeometry(TkMenuEntry *mePtr, Tk_Font tkfont,
	const Tk_FontMetrics *fmPtr, int *widthPtr, int *heightPtr);
static void GetMenuIndicatorGeometry(TkMenu *menuPtr, TkMenuEntry *mePtr,
	Tk_Font tkfont, const Tk_FontMetrics *fmPtr, int *widthPtr,
	int *heightPtr);
static void GetMenuSeparatorGeometry(TkMenu *menuPtr, TkMenuEntry *mePtr,
	Tk_Font tkfont, const Tk_FontMetrics *fmPtr, int *widthPtr,
	int *heightPtr);
static TkMenuEntry* GetParentMenuEntry(TkMenu *menuPtr);
static void GetTearoffEntryGeometry(TkMenu *menuPtr, TkMenuEntry *mePtr,
	Tk_Font tkfont, const Tk_FontMetrics *fmPtr, int *widthPtr,
	int *heightPtr);
static char FindMarkCharacter(TkMenuEntry *mePtr);
static int GetUtfMarkCharacter(char markChar, const char **markUtfPtr);
static TkMenu* MenuPtrForMenuRef(MenuRef menu);
static int ParseAccelerators(const char **accelStringPtr, int *modifierNumPtr,
	Tcl_UniChar *modifierUniChars, int *modifierWidth);
static void MenuSelectEvent(TkMenu *menuPtr);
static void ReconfigureIndividualMenu(TkMenu *menuPtr, MenuHandle macMenuHdl,
	int base);
static void ReconfigureMacintoshMenu(ClientData clientData);
static void RecursivelyClearActiveMenu(TkMenu *menuPtr);
static void RecursivelyDeleteMenu(TkMenu *menuPtr);
static void RecursivelyInsertMenu(TkMenu *menuPtr);
static void SetDefaultMenubar(void);
static int SetMenuCascade(TkMenu *menuPtr);

#ifdef USE_TK_MDEF
#define SCREEN_MARGIN 5
static MacDrawable macMDEFDrawable;
				/* Drawable for use by MDEF code */
static int MDEFScrollFlag = 0;	/* Used so that popups don't scroll too soon.*/
static MenuItemDrawingUPP tkThemeMenuItemDrawingUPP;
				/* Points to the UPP for theme Item drawing. */
static Tcl_Obj *useMDEFVar;

static void  DrawMenuBackground(TkMenu *menuPtr, Rect *menuRectPtr,
	Drawable d);
static void MenuDefProc(short message, MenuHandle menu, Rect *menuRectPtr,
	Point hitPt, short *whichItem );
static void HandleMenuHiliteMsg(MenuRef menu, Rect *menuRectPtr, Point hitPt,
	SInt16 *whichItem, TkMenu *menuPtr);
static void HandleMenuDrawMsg(MenuRef menu, Rect *menuRectPtr, Point hitPt,
	SInt16 *whichItem, TkMenu *menuPtr);
static void HandleMenuFindItemMsg(MenuRef menu, Rect *menuRectPtr,
	Point hitPt, SInt16 *whichItem, TkMenu *menuPtr);
static void HandleMenuPopUpMsg(MenuRef menu, Rect *menuRectPtr, Point hitPt,
	SInt16 *whichItem, TkMenu *menuPtr);
static void HandleMenuCalcItemMsg(MenuRef menu, Rect *menuRectPtr, Point hitPt,
	SInt16 *whichItem, TkMenu *menuPtr);
static void AppearanceEntryDrawWrapper(TkMenuEntry *mePtr, Rect * menuRectPtr,
	MenuTrackingData *mtdPtr, Drawable d, Tk_FontMetrics *fmPtr,
	Tk_Font tkfont, int erase);
static pascal void ThemeMenuItemDrawingProc(const Rect *inBounds,
	SInt16 inDepth, Boolean inIsColorDevice, SInt32 inUserData);
#else /* USE_TK_MDEF */
#   define useMDEF 0
#endif /* USE_TK_MDEF */

#define IS_THEME_MENU_FONT(tkfont) (strcmp(Tk_NameOfFont(tkfont), "menu") == 0)


/*
 *----------------------------------------------------------------------
 *
 * DrawThemeText --
 *
 *	Wrapper for DrawThemeTextBox API.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
DrawThemeText(
    Drawable d,
    GC gc,
    CFStringRef string,
    ThemeFontID font,
    ThemeDrawState drawState,
    const Rect* bounds,
    int baseline,
    int just)
{
    TkMacOSXDrawingContext dc;
    Rect adjustedBounds;

    /*
     * Menu item text drawn with the .Keyboard font (used for
     * kThemeMenuItemCmdKeyFont) won't always have the same ascent and
     * baseline as text drawn with the regular menu item font, since the
     * glyphs in the .Keyboard font may have a different height. Therefore, we
     * first determine the baseline of the text and then adjust the bounds
     * rect so the baseline aligns with the overall baseline of the menu item.
     */
    if (font == kThemeMenuItemCmdKeyFont) {
	Point size;
	SInt16 cmdKeyBaseline;

	GetThemeTextDimensions(string, font, drawState, false, &size,
		&cmdKeyBaseline);
	adjustedBounds = *bounds;
	OffsetRect(&adjustedBounds, 0, baseline - bounds->top - size.v -
		cmdKeyBaseline);
	bounds = &adjustedBounds;
    }
    if (TkMacOSXSetupDrawingContext(d, gc, 1, &dc)) {
	ChkErr(DrawThemeTextBox, string, font, drawState, false, bounds, just,
		dc.context);
	TkMacOSXRestoreDrawingContext(&dc);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MeasureThemeText --
 *
 *	Wrapper for GetThemeTextDimensions API.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
MeasureThemeText(
    CFStringRef string,
    ThemeFontID font)
{
    Point pt;

    ChkErr(GetThemeTextDimensions, string, font, kThemeStateActive, false, &pt,
	NULL);
    return pt.h;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXUseID --
 *
 *	Take the ID out of the available list for new menus. Used by the
 *	default menu bar's menus so that they do not get created at the tk
 *	level. See TkMacOSXGetNewMenuID for more information.
 *
 * Results:
 *	Returns TCL_OK if the id was not in use. Returns TCL_ERROR if the
 *	id was in use.
 *
 * Side effects:
 *	A hash table entry in the command table is created with a NULL
 *	value.
 *
 *----------------------------------------------------------------------
 */

int
TkMacOSXUseMenuID(
    short macID)		/* The id to take out of the table */
{
    Tcl_HashEntry *commandEntryPtr;
    int newEntry;
    int iMacID = macID; /* Do this to remove compiler warning */

    TkMenuInit();
    commandEntryPtr = Tcl_CreateHashEntry(&commandTable, (char *) iMacID,
	    &newEntry);
    if (!newEntry) {
	return TCL_ERROR;
    }
    Tcl_SetHashValue(commandEntryPtr, NULL);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXGetNewMenuID --
 *
 *	Allocates a new menu id and marks it in use. Each menu on the
 *	mac must be designated by a unique id, which is a short. In
 *	addition, some ids are reserved by the system. Since Tk uses
 *	mostly dynamic menus, we must allocate and free these ids on
 *	the fly. We use the id as a key into a hash table; if there
 *	is no hash entry, we know that we can use the id.
 *
 *	Carbon allows a much larger number of menus than the old APIs.
 *	I believe this is 32768, but am not sure. This code just uses
 *	2000 as the upper limit. Unfortunately tk leaks menus when
 *	cloning, under some circumstances (see bug on sourceforge).
 *
 * Results:
 *	Returns TCL_OK if succesful; TCL_ERROR if there are no more
 *	ids of the appropriate type to allocate. menuIDPtr contains
 *	the new id if succesful.
 *
 * Side effects:
 *	An entry is created for the menu in the command hash table,
 *	and the hash entry is stored in the appropriate field in the
 *	menu data structure.
 *
 *----------------------------------------------------------------------
 */

int
TkMacOSXGetNewMenuID(
    Tcl_Interp *interp,		/* Used for error reporting */
    TkMenu *menuPtr,		/* The menu we are working with */
    int cascade,		/* 0 if we are working with a normal menu;
				 * 1 if we are working with a cascade */
    short *menuIDPtr)		/* The resulting id */
{
    int found = 0;
    int newEntry;
    Tcl_HashEntry *commandEntryPtr = NULL;
    short returnID = *menuIDPtr;

    /*
     * The following code relies on shorts and unsigned chars wrapping
     * when the highest value is incremented. Also, the values between
     * 236 and 255 inclusive are reserved for DA's by the Mac OS.
     */

    if (!cascade) {
	short curID = lastMenuID + 1;

	if (curID == 236) {
	    curID = 256;
	}

	while (curID != lastMenuID) {
	    int iCurID = curID;
	    commandEntryPtr = Tcl_CreateHashEntry(&commandTable,
		    (char *) iCurID, &newEntry);
	    if (newEntry == 1) {
		found = 1;
		lastMenuID = returnID = curID;
		break;
	    }
	    curID++;
	    if (curID == 236) {
		curID = 256;
	    }
	}
    } else {
	/*
	 * Cascade ids must be between 0 and 235 only, so they must be
	 * dealt with separately.
	 */

	short curID = lastCascadeID + 1;

	if (curID == 2000) {
	    curID = 0;
	}

	while (curID != lastCascadeID) {
	    int iCurID = curID;
	    commandEntryPtr = Tcl_CreateHashEntry(&commandTable,
		    (char *) iCurID, &newEntry);
	    if (newEntry == 1) {
		found = 1;
		lastCascadeID = returnID = curID;
		break;
	    }
	    curID++;
	    if (curID == 2000) {
		curID = 0;
	    }
	}
    }

    if (!found) {
	Tcl_ResetResult(interp);
	Tcl_AppendResult(interp, "No more menus can be allocated.", NULL);
	return TCL_ERROR;
    }
    Tcl_SetHashValue(commandEntryPtr, (char *) menuPtr);
    *menuIDPtr = returnID;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXFreeMenuID --
 *
 *	Marks the id as free.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The hash table entry for the ID is cleared.
 *
 *----------------------------------------------------------------------
 */

void
TkMacOSXFreeMenuID(
    short menuID)		/* The id to free */
{
    Tcl_HashEntry *entryPtr = Tcl_FindHashEntry(&commandTable,
	    (char*)(intptr_t)menuID);

    if (entryPtr != NULL) {
	 Tcl_DeleteHashEntry(entryPtr);
    }
    if (menuID == currentAppleMenuID) {
	currentAppleMenuID = 0;
    }
    if (menuID == currentHelpMenuID) {
	currentHelpMenuID = 0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MenuPtrForMenuRef --
 *
 *	Returns a pointer to the TkMenu corresponding to a given
 *	Carbon MenuRef.
 *
 * Results:
 *	Returns a pointer to a TkMenu or NULL.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

TkMenu*
MenuPtrForMenuRef(
    MenuRef menu)
{
    TkMenu *menuPtr = NULL;
    MenuID menuID = GetMenuID(menu);
    Tcl_HashEntry *commandEntryPtr = Tcl_FindHashEntry(&commandTable,
	    (char*)(intptr_t)menuID);

    if (commandEntryPtr) {
	menuPtr = (TkMenu *) Tcl_GetHashValue(commandEntryPtr);
    }
    return menuPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * GetParentMenuEntry --
 *
 *	Returns a pointer to the parent's TkMenuEntry of a given TkMenu.
 *
 * Results:
 *	Returns a pointer to a TkMenuEntry or NULL.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

TkMenuEntry*
GetParentMenuEntry(
    TkMenu *menuPtr)
{
    TkMenuEntry *cascadeEntryPtr;

    for (cascadeEntryPtr = menuPtr->menuRefPtr->parentEntryPtr;
	    cascadeEntryPtr != NULL;
	    cascadeEntryPtr = cascadeEntryPtr->nextCascadePtr) {
	const char *name = (cascadeEntryPtr->namePtr == NULL) ? ""
		: Tcl_GetString(cascadeEntryPtr->namePtr);

	if (strcmp(name, Tk_PathName(menuPtr->tkwin)) == 0) {
	    break;
	}
    }
    return cascadeEntryPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TkpNewMenu --
 *
 *	Gets a new blank menu. Only the platform specific options are filled
 *	in.
 *
 * Results:
 *	Returns a standard TCL error.
 *
 * Side effects:
 *	Allocates a Macintosh menu handle and puts in the platformData
 *	field of the menuPtr.
 *
 *----------------------------------------------------------------------
 */

int
TkpNewMenu(
    TkMenu *menuPtr)		/* The common structure we are making the
				 * platform structure for. */
{
    short menuID;
    MenuRef macMenuHdl;
#ifdef USE_TK_MDEF
    MenuDefSpec menuDefSpec;
    Tcl_Obj *useMDEFObjPtr;
    int useMDEF = 1;
#endif
    int error = TCL_OK;
    OSStatus err;
    CFStringRef cfStr;

    error = TkMacOSXGetNewMenuID(menuPtr->interp, menuPtr, 0, &menuID);
    if (error != TCL_OK) {
	return error;
    }
    err = ChkErr(CreateNewMenu, menuID, kMenuAttrDoNotUseUserCommandKeys,
	    &macMenuHdl);
    if (err != noErr) {
	Tcl_AppendResult(menuPtr->interp, "CreateNewMenu failed.", NULL);
	return TCL_ERROR;
    }
    cfStr = CFStringCreateWithCString(NULL, Tk_PathName(menuPtr->tkwin),
	    kCFStringEncodingUTF8);
    if (!cfStr) {
	Tcl_AppendResult(menuPtr->interp, "CFStringCreateWithCString failed.",
		NULL);
	return TCL_ERROR;
    }
    err = ChkErr(SetMenuTitleWithCFString, macMenuHdl, cfStr);
    CFRelease(cfStr);
    if (err != noErr) {
	Tcl_AppendResult(menuPtr->interp, "SetMenuTitleWithCFString failed.",
		NULL);
	return TCL_ERROR;
    }

    menuPtr->platformData = (TkMenuPlatformData) ckalloc(sizeof(MacMenu));
    ((MacMenu *) menuPtr->platformData)->menuHdl = macMenuHdl;

#ifdef USE_TK_MDEF
    /*
     * Check whether we want to use the custom mdef or not. For now
     * the default is to use it unless the variable is explicitly
     * set to no.
     */

    useMDEFObjPtr = Tcl_ObjGetVar2(menuPtr->interp, useMDEFVar, NULL,
	    TCL_GLOBAL_ONLY);
    if (useMDEFObjPtr == NULL || Tcl_GetBooleanFromObj(NULL, useMDEFObjPtr,
	    &useMDEF) == TCL_ERROR || useMDEF) {
	menuDefSpec.defType = kMenuDefProcPtr;
	menuDefSpec.u.defProc = MenuDefProc;
	ChkErr(SetMenuDefinition, macMenuHdl, &menuDefSpec);
    }
    ((MacMenu *) menuPtr->platformData)->useMDEF = useMDEF;
#endif /* USE_TK_MDEF */

    if ((currentMenuBarInterp == menuPtr->interp)
	    && (currentMenuBarName != NULL)) {
	Tk_Window parentWin = Tk_Parent(menuPtr->tkwin);

	if (strcmp(currentMenuBarName, Tk_PathName(parentWin)) == 0) {
	    if ((strcmp(Tk_PathName(menuPtr->tkwin)
		    + strlen(Tk_PathName(parentWin)), ".apple") == 0)
		    || (strcmp(Tk_PathName(menuPtr->tkwin)
		    + strlen(Tk_PathName(parentWin)), ".help") == 0)) {
		if (!(menuBarFlags & MENUBAR_REDRAW_PENDING)) {
		    Tcl_DoWhenIdle(DrawMenuBarWhenIdle, NULL);
		    menuBarFlags |= MENUBAR_REDRAW_PENDING;
		}
	    }
	}
    }

    menuPtr->menuFlags |= MENU_RECONFIGURE_PENDING;
    Tcl_DoWhenIdle(ReconfigureMacintoshMenu, (ClientData) menuPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TkpDestroyMenu --
 *
 *	Destroys platform-specific menu structures.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	All platform-specific allocations are freed up.
 *
 *----------------------------------------------------------------------
 */

void
TkpDestroyMenu(
    TkMenu *menuPtr)		/* The common menu structure */
{
    MenuRef macMenuHdl = ((MacMenu *) menuPtr->platformData)->menuHdl;

    if (menuPtr->menuFlags & MENU_RECONFIGURE_PENDING) {
	Tcl_CancelIdleCall(ReconfigureMacintoshMenu, (ClientData) menuPtr);
	menuPtr->menuFlags &= ~MENU_RECONFIGURE_PENDING;
    }
    if (GetMenuID(macMenuHdl) == currentHelpMenuID) {
	MenuRef helpMenuHdl;
	MenuItemIndex helpIndex;

	if ((HMGetHelpMenu(&helpMenuHdl,&helpIndex) == noErr)
		&& (helpMenuHdl != NULL)) {
	    int i, count = CountMenuItems(helpMenuHdl);

	    for (i = helpIndex; i <= count; i++) {
		DeleteMenuItem(helpMenuHdl, helpIndex);
	    }
	}
	currentHelpMenuID = 0;
    }
    if (menuPtr->platformData != NULL) {
	MenuID menuID = GetMenuID(macMenuHdl);

	DeleteMenu(menuID);
	TkMacOSXFreeMenuID(menuID);
	DisposeMenu(macMenuHdl);
	ckfree((char *) menuPtr->platformData);
	menuPtr->platformData = NULL;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * SetMenuCascade --
 *
 *	Does any cleanup to change a menu from a normal to a cascade.
 *
 * Results:
 *	Standard Tcl error.
 *
 * Side effects:
 *	The mac menu id is reset.
 *
 *----------------------------------------------------------------------
 */

int
SetMenuCascade(
    TkMenu* menuPtr)		/* The menu we are setting up to be a
				 * cascade. */
{
    MenuHandle macMenuHdl = ((MacMenu *) menuPtr->platformData)->menuHdl;
    MenuID newMenuID, menuID = GetMenuID(macMenuHdl);
    int error = TCL_OK;

    if (menuID >= 256) {
	error = TkMacOSXGetNewMenuID(menuPtr->interp, menuPtr, 1, &newMenuID);
	if (error == TCL_OK) {
	    TkMacOSXFreeMenuID(menuID);
	    SetMenuID(macMenuHdl,newMenuID);
	}
    }
    return error;
}

/*
 *----------------------------------------------------------------------
 *
 * TkpDestroyMenuEntry --
 *
 *	Cleans up platform-specific menu entry items.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	All platform-specific allocations are freed up.
 *
 *----------------------------------------------------------------------
 */

void
TkpDestroyMenuEntry(
    TkMenuEntry *mePtr)		/* The common structure for the menu entry. */
{
    TkMenu *menuPtr = mePtr->menuPtr;

    ckfree((char *) mePtr->platformEntryData);
    if ((menuPtr->platformData != NULL)
	    && !(menuPtr->menuFlags & MENU_RECONFIGURE_PENDING)) {
	menuPtr->menuFlags |= MENU_RECONFIGURE_PENDING;
	Tcl_DoWhenIdle(ReconfigureMacintoshMenu, (ClientData) menuPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetEntryText --
 *
 *	Given a menu entry, gives back the text that should go in it.
 *	Separators should be done by the caller, as they have to be
 *	handled specially. This is primarily used to do a substitution
 *	between "..." and the ellipsis character which looks nicer.
 *
 * Results:
 *	itemText points to the new text for the item.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
GetEntryText(
    TkMenuEntry *mePtr,		/* A pointer to the menu entry. */
    Tcl_DString *dStringPtr)	/* The DString to put the text into. This
				 * will be initialized by this routine. */
{
#ifdef USE_TK_MDEF
    const int useMDEF = ((MacMenu *) mePtr->menuPtr->platformData)->useMDEF;
#endif
    int noLabel = (mePtr->labelPtr == NULL || mePtr->labelLength == 0);

    Tcl_DStringInit(dStringPtr);
    if (mePtr->type == TEAROFF_ENTRY && (useMDEF || noLabel)) {
	Tcl_DStringAppend(dStringPtr, "(Tear-off)", -1);
    } else if (mePtr->imagePtr != NULL && (useMDEF || noLabel) &&
	    mePtr->compound == COMPOUND_NONE) {
	Tcl_DStringAppend(dStringPtr, "(Image)", -1);
    } else if (mePtr->bitmapPtr != NULL && (useMDEF || noLabel) &&
	    mePtr->compound == COMPOUND_NONE) {
	Tcl_DStringAppend(dStringPtr, "(Pixmap)", -1);
    } else if (noLabel) {
	/*
	 * The Mac menu manager does not like null strings.
	 */

	Tcl_DStringAppend(dStringPtr, " ", -1);
    } else {
	int length;
	char *text = Tcl_GetStringFromObj(mePtr->labelPtr, &length);
	char *dStringText;
	int i;

	for (i = 0; *text; text++, i++) {
	    if ((*text == '.') && (*(text+1) == '.') && (*(text+2) == '.')) {
		Tcl_DStringAppend(dStringPtr, menuSymbols[ELLIPSIS_SYMBOL].utf,
			menuSymbols[ELLIPSIS_SYMBOL].utfLen);
		i += menuSymbols[ELLIPSIS_SYMBOL].utfLen - 1;
		text += 2;
	    } else {
		Tcl_DStringSetLength(dStringPtr,
			Tcl_DStringLength(dStringPtr) + 1);
		dStringText = Tcl_DStringValue(dStringPtr);
		dStringText[i] = *text;
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * FindMarkCharacter --
 *
 *	Finds the Macintosh mark character based on the font of the
 *	item. We calculate a good mark character based on the font
 *	that this item is rendered in.
 *
 * Results:
 *	Mark char.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char
FindMarkCharacter(
    TkMenuEntry *mePtr)		/* The entry we are finding the character
				 * for. */
{
    static const char markChars[] = {kCheckCharCode, kDiamondCharCode,
	    kBulletCharCode, '-', kCheckCharCode};
    const char *markChar = markChars;
    int i = sizeof(markChars);
    Tk_Font tkfont;

    tkfont = Tk_GetFontFromObj(mePtr->menuPtr->tkwin,
	    (mePtr->fontPtr == NULL) ? mePtr->menuPtr->fontPtr
	    : mePtr->fontPtr);

    while (--i) {
	if (!TkMacOSXIsCharacterMissing(tkfont, *markChar)) {
	    break;
	}
	markChar++;
    }
    return *markChar;
}

/*
 *----------------------------------------------------------------------
 *
 * GetUtfMarkCharacter --
 *
 *	Get the utf8 string for the given mark character, taking into
 *	account the special menu font char codes.
 *
 * Results:
 *	Length of returned utf8 string.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
GetUtfMarkCharacter(
    char markChar,
    const char **markUtfPtr)
{
    const MenuSymbol *ms = menuSymbols;
    int len = 0;

    while (ms->unicode) {
	if (ms->charCode && ms->charCode == markChar) {
	    *markUtfPtr = ms->utf;
	    len = ms->utfLen;
	    break;
	}
	ms++;
    }
    if (!len) {
	static char markUtf[TCL_UTF_MAX + 1];

	Tcl_ExternalToUtf(NULL, TkMacOSXCarbonEncoding, &markChar, 1, 0, NULL,
		markUtf, TCL_UTF_MAX + 1, NULL, &len, NULL);
	*markUtfPtr = markUtf;
    }
    return len;
}

/*
 *----------------------------------------------------------------------
 *
 * ParseAccelerators --
 *
 *	Parse menu accelerator string.
 *
 * Results:
 *	Accelerator flags.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
ParseAccelerators(
    const char **accelStringPtr,
    int *modifierNumPtr,
    Tcl_UniChar *modifierUniChars,
    int *modifierWidth)
{
    struct Modif {
	const char *name;
	const size_t len;
	const int flag, symbol;
    };
#define MODIF(n, f) { #n, sizeof(#n)-1, ENTRY_##f##_ACCEL, f##_SYMBOL }
    static const struct Modif modifs[] = {
	MODIF(Control,	CONTROL),
	MODIF(Ctrl,	CONTROL),
	MODIF(Option,	OPTION),
	MODIF(Opt,	OPTION),
	MODIF(Alt,	OPTION),
	MODIF(Shift,	SHIFT),
	MODIF(Command,	COMMAND),
	MODIF(Cmd,	COMMAND),
	MODIF(Meta,	COMMAND),
	{ NULL, 0, 0, 0}
    };
#undef MODIF
    const char *accelString = *accelStringPtr;
    int flags = 0, num = 0, seen = 0, width = 0;
    const struct Modif *m;

    while (1) {
	m = modifs;
	while (m->name) {
	    int l = m->len;

	    if (!strncasecmp(accelString, m->name, l) &&
		    (accelString[l] == '-' || accelString[l] == '+')) {
		flags |= m->flag;
		accelString += l+1;
		break;
	    }
	    m++;
	}
	if (!m->name || !*accelString) {
	    break;
	}
    }
    m = modifs;
    while (m->name && num < MODIFIER_NUM) {
	if (flags & m->flag && !(seen & m->flag)) {
	    modifierUniChars[num++] = menuSymbols[m->symbol].unicode;
	    width += menuSymbols[m->symbol].width;
	    seen |= m->flag;
	}
	m++;
    }
    *accelStringPtr = accelString;
    *modifierNumPtr = num;
    *modifierWidth = width;
    return flags;
}

/*
 *----------------------------------------------------------------------
 *
 * TkpConfigureMenuEntry --
 *
 *	Processes configurations for menu entries.
 *
 * Results:
 *	Returns standard TCL result. If TCL_ERROR is returned, then
 *	the interp's result contains an error message.
 *
 * Side effects:
 *	Configuration information get set for mePtr; old resources
 *	get freed, if any need it.
 *
 *----------------------------------------------------------------------
 */

int
TkpConfigureMenuEntry(
    TkMenuEntry *mePtr) 	/* Information about menu entry; may
				 * or may not already have values for
				 * some fields. */
{
    TkMenu *menuPtr = mePtr->menuPtr;
    EntryGeometry *geometryPtr = (EntryGeometry *) mePtr->platformEntryData;

    /*
     * Cascade menus have to have menu IDs of less than 256. So
     * we need to change the child menu if this has been configured
     * for a cascade item.
     */

    if (mePtr->type == CASCADE_ENTRY) {
	if ((mePtr->childMenuRefPtr != NULL)
		&& (mePtr->childMenuRefPtr->menuPtr != NULL)) {
	    MenuHandle childMenuHdl = ((MacMenu *) mePtr
		    ->childMenuRefPtr->menuPtr->platformData)->menuHdl;

	    if (childMenuHdl != NULL) {
		int error = SetMenuCascade(mePtr->childMenuRefPtr->menuPtr);

		if (error != TCL_OK) {
		    return error;
		}

		if (menuPtr->menuType == MENUBAR) {
		    CFStringRef cfStr = CFStringCreateWithCString(NULL,
			    (!(mePtr->labelPtr) ? "" :
			    Tcl_GetString(mePtr->labelPtr)),
			    kCFStringEncodingUTF8);

		    if (cfStr) {
			SetMenuTitleWithCFString(childMenuHdl, cfStr);
			CFRelease(cfStr);
		    }
		}
	    }
	}
    }

    /*
     * We need to parse the accelerator string. If it has the strings
     * for Command, Control, Shift or Option, we need to flag it
     * so we can draw the symbols for it. We also need to precalcuate
     * the position of the first real character we are drawing.
     */

    if (0 == mePtr->accelLength) {
	geometryPtr->accelTextStart = -1;
    } else {
	const char *accelString = (mePtr->accelPtr == NULL) ? ""
		: Tcl_GetString(mePtr->accelPtr);
	const char *accelStart = accelString;

	mePtr->entryFlags &= ~ENTRY_ACCEL_MASK;
	mePtr->entryFlags |= ParseAccelerators(&accelString,
		&geometryPtr->modifierNum, geometryPtr->modifierUniChars,
		&geometryPtr->modifierWidth);
	geometryPtr->accelTextStart = (ptrdiff_t)(accelString - accelStart);
    }

    if (!(menuPtr->menuFlags & MENU_RECONFIGURE_PENDING)) {
	menuPtr->menuFlags |= MENU_RECONFIGURE_PENDING;
	Tcl_DoWhenIdle(ReconfigureMacintoshMenu, (ClientData) menuPtr);
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ReconfigureIndividualMenu --
 *
 *	This routine redoes the guts of the menu. It works from
 *	a base item and offset, so that a regular menu will
 *	just have all of its items added, but the help menu will
 *	have all of its items appended after the apple-defined
 *	items.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The Macintosh menu handle is updated
 *
 *----------------------------------------------------------------------
 */

void
ReconfigureIndividualMenu(
    TkMenu *menuPtr,		/* The menu we are affecting. */
    MenuHandle macMenuHdl,	/* The macintosh menu we are affecting.
				 * Will not necessarily be
				 * menuPtr->platformData because this could
				 * be the help menu. */
    int base)			/* The last index that we do not want
				 * touched. 0 for normal menus;
				 * # of system help menu items
				 * for help menus. */
{
    int count;
    int index;
    TkMenuEntry *mePtr;
    int parentDisabled = 0;

#ifdef TK_MAC_DEBUG_MENUS
    /*
     * Carbon-internal menu debugging (c.f. Technote 2124)
     */

    TkMacOSXInitNamedDebugSymbol(HIToolbox, void, DebugPrintMenu,
	MenuRef menu);
    if (DebugPrintMenu) {
	DebugPrintMenu(macMenuHdl);
    }
#endif

    mePtr = GetParentMenuEntry(menuPtr);
    if (mePtr && mePtr->state == ENTRY_DISABLED) {
	parentDisabled = 1;
    }

    /*
     * First, we get rid of all of the old items.
     */

    count = CountMenuItems(macMenuHdl);
    for (index = base; index < count; index++) {
	DeleteMenuItem(macMenuHdl, base + 1);
    }

    count = menuPtr->numEntries;

    for (index = 1; index <= count; index++) {
	mePtr = menuPtr->entries[index - 1];

	/*
	 * We have to do separators separately because SetMenuItemText
	 * does not parse meta-characters.
	 */

	if (mePtr->type == SEPARATOR_ENTRY) {
	    AppendMenuItemTextWithCFString(macMenuHdl, NULL,
		    kMenuItemAttrSeparator | kMenuItemAttrDisabled, 0, NULL);
	} else {
	    Tcl_DString itemTextDString;
	    CFStringRef cfStr;

	    GetEntryText(mePtr, &itemTextDString);
	    cfStr = CFStringCreateWithCString(NULL,
		    Tcl_DStringValue(&itemTextDString), kCFStringEncodingUTF8);
	    if (cfStr) {
		AppendMenuItemTextWithCFString(macMenuHdl, cfStr, 0, 0, NULL);
		CFRelease(cfStr);
	    } else {
		AppendMenuItemTextWithCFString(macMenuHdl, CFSTR ("<Error>"),
		    0, 0, NULL);
	    }
	    Tcl_DStringFree(&itemTextDString);

	    /*
	     * Set enabling and disabling correctly.
	     */

	    if (parentDisabled || (mePtr->state == ENTRY_DISABLED)) {
		DisableMenuItem(macMenuHdl, base + index);
	    } else {
		EnableMenuItem(macMenuHdl, base + index);
	    }

	    /*
	     * Set the check mark for check entries and radio entries.
	     */

	    SetItemMark(macMenuHdl, base + index, 0);
	    if ((mePtr->type == CHECK_BUTTON_ENTRY)
		    || (mePtr->type == RADIO_BUTTON_ENTRY)) {
		CheckMenuItem(macMenuHdl, base + index, (mePtr->entryFlags
		& ENTRY_SELECTED) && mePtr->indicatorOn);
		if (mePtr->indicatorOn
			&& (mePtr->entryFlags & ENTRY_SELECTED)) {
		    SetItemMark(macMenuHdl, base + index,
			    FindMarkCharacter(mePtr));
		}
	    }

	    if (mePtr->type == CASCADE_ENTRY) {
		if ((mePtr->childMenuRefPtr != NULL)
			&& (mePtr->childMenuRefPtr->menuPtr != NULL)) {
		    MenuHandle childMenuHdl =
			    ((MacMenu *) mePtr->childMenuRefPtr
			    ->menuPtr->platformData)->menuHdl;

		    if (childMenuHdl != NULL) {
			ChkErr(SetMenuItemHierarchicalID, macMenuHdl,
				base + index, GetMenuID(childMenuHdl));
		    }
		    /*
		     * If we changed the highligthing of this menu, its
		     * children all have to be reconfigured so that
		     * their state will be reflected in the menubar.
		     */

		    if (!(mePtr->childMenuRefPtr->menuPtr->menuFlags
				& MENU_RECONFIGURE_PENDING)) {
			mePtr->childMenuRefPtr->menuPtr->menuFlags
				|= MENU_RECONFIGURE_PENDING;
			Tcl_DoWhenIdle(ReconfigureMacintoshMenu,
				(ClientData) mePtr->childMenuRefPtr->menuPtr);
		    }
		}
	    }

	    if ((mePtr->type != CASCADE_ENTRY) && (mePtr->accelPtr != NULL)) {
		int accelLen, modifiers = 0, hasCmd = 0;
		EntryGeometry *geometryPtr =
			(EntryGeometry*)mePtr->platformEntryData;
		int offset = geometryPtr->accelTextStart;
		char *accel = Tcl_GetStringFromObj(mePtr->accelPtr, &accelLen);

		accelLen -= offset;
		accel += offset;
		if (mePtr->entryFlags & ENTRY_OPTION_ACCEL) {
		    modifiers |= kMenuOptionModifier;
		}
		if (mePtr->entryFlags & ENTRY_SHIFT_ACCEL) {
		    modifiers |= kMenuShiftModifier;
		}
		if (mePtr->entryFlags & ENTRY_CONTROL_ACCEL) {
		    modifiers |= kMenuControlModifier;
		}
		if (mePtr->entryFlags & ENTRY_COMMAND_ACCEL) {
		    hasCmd = 1;
		}
		if (accelLen == 1) {
		    if (hasCmd || (modifiers != 0 && modifiers !=
			    kMenuShiftModifier)) {
			SetItemCmd(macMenuHdl, base + index, accel[0]);
			if (!hasCmd) {
			    modifiers |= kMenuNoCommandModifier;
			}
		    }
		} else {
		    /*
		     * Convert from accelerator names to Carbon menu glyphs.
		     */
		    struct Glyph {
			const char *name;
			const size_t len;
			const char glyph;
		    };
#define GLYPH(n, g) { #n, sizeof(#n)-1, kMenu##g##Glyph }
		    static const struct Glyph glyphs[] = {
			GLYPH(PageUp,	PageUp),
			GLYPH(PageDown, PageDown),
			GLYPH(Left,	LeftArrow),
			GLYPH(Right,	RightArrow),
			GLYPH(Up,	UpArrow),
			GLYPH(Down,	DownArrow),
			GLYPH(Escape,	Escape),
			GLYPH(Clear,	Clear),
			GLYPH(Enter,	Enter),
			GLYPH(Backspace,DeleteLeft),
			GLYPH(Space,	Space),
			GLYPH(Tab,	TabRight),
			GLYPH(Delete,	DeleteRight),
			GLYPH(Home,	NorthwestArrow),
			GLYPH(End,	SoutheastArrow),
			GLYPH(Return,	Return),
			GLYPH(Help,	Help),
			GLYPH(Power,	Power),
			{ NULL, 0, 0}
		    };
#undef GLYPH
		    const struct Glyph *g = glyphs;
		    char glyph = 0;

		    if (accel[0] == 'F' && accelLen < 4 &&
			    (accel[1] > '0' && accel[1] <= '9')) {
			int fkey = accel[1] - '0';

			if (accelLen == 3) {
			    if (accel[2] >= '0' && accel[2] <= '9') {
				fkey = 10 * fkey + (accel[2] - '0');
			    } else {
				fkey = 0;
			    }
			}
			if (fkey >= 1 && fkey <= 12) {
			    glyph = kMenuF1Glyph + fkey - 1;
			} else if (fkey >= 13 && fkey <= 15) {
			    glyph = kMenuF13Glyph + fkey - 13;
			}
		    } else while (g->name) {
			if (accel[0] == g->name[0] &&
				(size_t)accelLen == g->len &&
				!strncasecmp(accel, g->name, g->len)) {
			    glyph = g->glyph;
			    break;
			}
			g++;
		    }
		    if (glyph) {
			ChkErr(SetMenuItemKeyGlyph, macMenuHdl, base + index,
				glyph);
			if (!hasCmd) {
			    modifiers |= kMenuNoCommandModifier;
			}
			geometryPtr->accelGlyph = glyph;
		    }
		}
		ChkErr(SetMenuItemModifiers, macMenuHdl, base + index,
			modifiers);
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ReconfigureMacintoshMenu --
 *
 *	Rebuilds the Macintosh MenuHandle items from the menu. Called
 *	usually as an idle handler, but can be called synchronously
 *	if the menu is about to be posted.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Configuration information get set for mePtr; old resources
 *	get freed, if any need it.
 *
 *----------------------------------------------------------------------
 */

void
ReconfigureMacintoshMenu(
    ClientData clientData)	/* Information about menu entry; may
				 * or may not already have values for
				 * some fields. */
{
    TkMenu *menuPtr = (TkMenu *) clientData;
    MenuHandle macMenuHdl = ((MacMenu *) menuPtr->platformData)->menuHdl;
    MenuHandle helpMenuHdl = NULL;

    menuPtr->menuFlags &= ~MENU_RECONFIGURE_PENDING;

    if (NULL == macMenuHdl) {
	return;
    }

    ReconfigureIndividualMenu(menuPtr, macMenuHdl, 0);

    if (GetMenuID(macMenuHdl) == currentHelpMenuID) {
	MenuItemIndex helpIndex;
	HMGetHelpMenu(&helpMenuHdl,&helpIndex);
	if (helpMenuHdl != NULL) {
	    ReconfigureIndividualMenu(menuPtr, helpMenuHdl, helpIndex - 1);
	}
    }

    if (menuPtr->menuType == MENUBAR) {
	if (!(menuBarFlags & MENUBAR_REDRAW_PENDING)) {
	    Tcl_DoWhenIdle(DrawMenuBarWhenIdle, NULL);
	    menuBarFlags |= MENUBAR_REDRAW_PENDING;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * CompleteIdlers --
 *
 *	Completes all idle handling so that the menus are in sync when
 *	the user invokes them with the mouse.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The Macintosh menu handles are flushed out.
 *
 *----------------------------------------------------------------------
 */

void
CompleteIdlers(
    TkMenu *menuPtr)		/* The menu we are completing. */
{
    int i;

    if (menuPtr->menuFlags & MENU_RECONFIGURE_PENDING) {
	Tcl_CancelIdleCall(ReconfigureMacintoshMenu, (ClientData) menuPtr);
	ReconfigureMacintoshMenu((ClientData) menuPtr);
    }

    for (i = 0; i < menuPtr->numEntries; i++) {
	if ((menuPtr->entries[i]->type == CASCADE_ENTRY) &&
		(menuPtr->entries[i]->childMenuRefPtr != NULL) &&
		(menuPtr->entries[i]->childMenuRefPtr->menuPtr != NULL)) {
	    CompleteIdlers(menuPtr->entries[i]->childMenuRefPtr->menuPtr);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkpPostMenu --
 *
 *	Posts a menu on the screen
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The menu is posted and handled.
 *
 *----------------------------------------------------------------------
 */

int
TkpPostMenu(
    Tcl_Interp *interp,		/* The interpreter this menu lives in */
    TkMenu *menuPtr,		/* The menu we are posting */
    int x,			/* The global x-coordinate of the top, left-
				 * hand corner of where the menu is supposed
				 * to be posted. */
    int y)			/* The global y-coordinate */
{
    MenuHandle macMenuHdl = ((MacMenu *) menuPtr->platformData)->menuHdl;
    long popUpResult;
    int result;

    if (inPostMenu > 0) {
	Tcl_AppendResult(interp,
		"Cannot call post menu while already posting menu", NULL);
	result = TCL_ERROR;
    } else {
	short menuID;
	Window window;
	int oldWidth = menuPtr->totalWidth;

	inPostMenu++;
	result = TkPreprocessMenu(menuPtr);
	/*
	 * The post commands could have deleted the menu, which means
	 * we are dead and should go away.
	 */

	if (result != TCL_OK || !menuPtr->tkwin) {
	    goto endPostMenu;
	}

	CompleteIdlers(menuPtr);
	if (menuBarFlags & MENUBAR_REDRAW_PENDING) {
	    Tcl_CancelIdleCall(DrawMenuBarWhenIdle, NULL);
	    DrawMenuBarWhenIdle(NULL);
	}
	RecursivelyInsertMenu(menuPtr);

	TkMacOSXTrackingLoop(1);
	popUpResult = PopUpMenuSelect(macMenuHdl, y, x, menuPtr->active);
	TkMacOSXTrackingLoop(0);
	menuPtr->totalWidth = oldWidth;

	/*
	 * Simulate the mouse up.
	 */

	window = Tk_WindowId(menuPtr->tkwin);
	TkGenerateButtonEventForXPointer(window);

	/*
	 * Dispatch the command.
	 */

	menuID = HiWord(popUpResult);
	if (menuID != 0) {
	    result = TkMacOSXDispatchMenuEvent(menuID, LoWord(popUpResult));
	}

endPostMenu:
	inPostMenu--;
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * TkpMenuNewEntry --
 *
 *	Adds a pointer to a new menu entry structure with the platform-
 *	specific fields filled in. The Macintosh uses the
 *	platformEntryData field of the TkMenuEntry record to store
 *	geometry information.
 *
 * Results:
 *	Standard TCL error.
 *
 * Side effects:
 *	Storage gets allocated. New menu entry data is put into the
 *	platformEntryData field of the mePtr.
 *
 *----------------------------------------------------------------------
 */

int
TkpMenuNewEntry(
    TkMenuEntry *mePtr)		/* The menu we are adding an entry to */
{
    EntryGeometry *geometryPtr =
	    (EntryGeometry *) ckalloc(sizeof(EntryGeometry));
    TkMenu *menuPtr = mePtr->menuPtr;

    geometryPtr->accelTextStart = 0;
    geometryPtr->accelTextWidth = 0;
    geometryPtr->nonAccelMargin = 0;
    geometryPtr->modifierWidth = 0;
    geometryPtr->modifierNum = 0;
    geometryPtr->accelGlyph = 0;
    mePtr->platformEntryData = (TkMenuPlatformEntryData) geometryPtr;
    if (!(menuPtr->menuFlags & MENU_RECONFIGURE_PENDING)) {
	menuPtr->menuFlags |= MENU_RECONFIGURE_PENDING;
	Tcl_DoWhenIdle(ReconfigureMacintoshMenu, (ClientData) menuPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_MacOSXTurnOffMenus --
 *
 *	Turns off all the menu drawing code. This is more than just disabling
 *	the "menu" command, this means that Tk will NEVER touch the menubar.
 *	It is needed in the Plugin, where Tk does not own the menubar.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A flag is set which will disable all menu drawing.
 *
 *----------------------------------------------------------------------
 */

void
Tk_MacOSXTurnOffMenus(void)
{
    gNoTkMenus = 1;
}

/*
 *----------------------------------------------------------------------
 *
 * DrawMenuBarWhenIdle --
 *
 *	Update the menu bar next time there is an idle event.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Menu bar is redrawn.
 *
 *----------------------------------------------------------------------
 */

void
DrawMenuBarWhenIdle(
    ClientData clientData)	/* ignored here */
{
    TkMenuReferences *menuRefPtr;
    TkMenu *appleMenuPtr, *helpMenuPtr, *menuBarPtr = NULL;
    MenuHandle macMenuHdl;
    Tcl_HashEntry *hashEntryPtr;

    /*
     * If we have been turned off, exit.
     */

    if (gNoTkMenus) {
	return;
    }

    /*
     * We need to clear the apple and help menus of any extra items.
     */

    if (currentAppleMenuID != 0) {
	hashEntryPtr = Tcl_FindHashEntry(&commandTable,
		(char*)(intptr_t)currentAppleMenuID);
	appleMenuPtr = (TkMenu *) Tcl_GetHashValue(hashEntryPtr);
	TkpDestroyMenu(appleMenuPtr);
	TkpNewMenu(appleMenuPtr);
	appleMenuPtr->menuFlags &= ~MENU_APPLE_MENU;
	appleMenuPtr->menuFlags |= MENU_RECONFIGURE_PENDING;
	Tcl_DoWhenIdle(ReconfigureMacintoshMenu, (ClientData) appleMenuPtr);
    }

    if (currentHelpMenuID != 0) {
	hashEntryPtr = Tcl_FindHashEntry(&commandTable,
		(char*)(intptr_t)currentHelpMenuID);
	helpMenuPtr = (TkMenu *) Tcl_GetHashValue(hashEntryPtr);
	TkpDestroyMenu(helpMenuPtr);
	TkpNewMenu(helpMenuPtr);
	helpMenuPtr->menuFlags &= ~MENU_HELP_MENU;
	helpMenuPtr->menuFlags |= MENU_RECONFIGURE_PENDING;
	Tcl_DoWhenIdle(ReconfigureMacintoshMenu,
		(ClientData) helpMenuPtr);
    }

    /*
     * We need to find the clone of this menu that is the menubar.
     * Once we do that, for every cascade in the menu, we need to
     * insert the Mac menu in the Mac menubar. Finally, we need
     * to redraw the menubar.
     */

    menuRefPtr = NULL;
    if (currentMenuBarName != NULL) {
	menuRefPtr = TkFindMenuReferences(currentMenuBarInterp,
		currentMenuBarName);
    }
    if (menuRefPtr) {
	TkMenu *menuPtr;
	TkMenu *cascadeMenuPtr;
	char *appleMenuName, *helpMenuName;
	int appleIndex = -1, helpIndex = -1, i;

	menuPtr = menuRefPtr->menuPtr;
	if (menuPtr != NULL) {
	    TkMenuReferences *specialMenuRefPtr;
	    TkMenuEntry *specialEntryPtr;

	    appleMenuName = ckalloc(strlen(currentMenuBarName) + 1 +
		    strlen(".apple") + 1);
	    sprintf(appleMenuName, "%s.apple", Tk_PathName(menuPtr->tkwin));
	    specialMenuRefPtr = TkFindMenuReferences(currentMenuBarInterp,
		    appleMenuName);
	    if ((specialMenuRefPtr != NULL)
		    && (specialMenuRefPtr->menuPtr != NULL)) {
		for (specialEntryPtr = specialMenuRefPtr->parentEntryPtr;
			specialEntryPtr != NULL;
			specialEntryPtr = specialEntryPtr->nextCascadePtr) {
		    if (specialEntryPtr->menuPtr == menuPtr) {
			appleIndex = specialEntryPtr->index;
			break;
		    }
		}
	    }
	    ckfree(appleMenuName);

	    helpMenuName = ckalloc(strlen(currentMenuBarName) + 1 +
		    strlen(".help") + 1);
	    sprintf(helpMenuName, "%s.help", Tk_PathName(menuPtr->tkwin));
	    specialMenuRefPtr = TkFindMenuReferences(currentMenuBarInterp,
		    helpMenuName);
	    if ((specialMenuRefPtr != NULL)
		    && (specialMenuRefPtr->menuPtr != NULL)) {
		for (specialEntryPtr = specialMenuRefPtr->parentEntryPtr;
			specialEntryPtr != NULL;
			specialEntryPtr = specialEntryPtr->nextCascadePtr) {
		    if (specialEntryPtr->menuPtr == menuPtr) {
			helpIndex = specialEntryPtr->index;
			break;
		    }
		}
	    }
	    ckfree(helpMenuName);
	}

	for (menuBarPtr = menuPtr;
		(menuBarPtr != NULL) && (menuBarPtr->menuType != MENUBAR);
		menuBarPtr = menuBarPtr->nextInstancePtr) {
	    /*
	     * Null loop body.
	     */
	}

	if (menuBarPtr) {
	    if (menuBarPtr->tearoff != menuPtr->tearoff) {
		if (menuBarPtr->tearoff) {
		    appleIndex = (-1 == appleIndex) ? appleIndex
			    : appleIndex + 1;
		    helpIndex = (-1 == helpIndex) ? helpIndex
			    : helpIndex + 1;
		} else {
		    appleIndex = (-1 == appleIndex) ? appleIndex
			    : appleIndex - 1;
		    helpIndex = (-1 == helpIndex) ? helpIndex
			    : helpIndex - 1;
		}
	    }
	    ClearMenuBar();

	    if (appleIndex == -1) {
		InsertMenu(tkAppleMenu, 0);
		currentAppleMenuID = 0;
		tkCurrentAppleMenu = tkAppleMenu;
	    } else {
		short appleID;

		appleMenuPtr = menuBarPtr->entries[appleIndex]
			->childMenuRefPtr->menuPtr;
		TkpDestroyMenu(appleMenuPtr);
		TkMacOSXGetNewMenuID(appleMenuPtr->interp, appleMenuPtr, 0,
			&appleID);
		macMenuHdl = NewMenu(appleID, "\p\024");
		appleMenuPtr->platformData =
			(TkMenuPlatformData) ckalloc(sizeof(MacMenu));
		((MacMenu *)appleMenuPtr->platformData)->menuHdl
			= macMenuHdl;
		appleMenuPtr->menuFlags |= MENU_APPLE_MENU;
		if (!(appleMenuPtr->menuFlags
			& MENU_RECONFIGURE_PENDING)) {
		    appleMenuPtr->menuFlags |= MENU_RECONFIGURE_PENDING;
		    Tcl_DoWhenIdle(ReconfigureMacintoshMenu,
			    (ClientData) appleMenuPtr);
		}
		InsertMenu(macMenuHdl, 0);
		RecursivelyInsertMenu(appleMenuPtr);
		currentAppleMenuID = appleID;
		tkCurrentAppleMenu = macMenuHdl;
	    }
	    if (helpIndex == -1) {
		currentHelpMenuID = 0;
	    }

	    for (i = 0; i < menuBarPtr->numEntries; i++) {
		if (i == appleIndex) {
		    if (menuBarPtr->entries[i]->state == ENTRY_DISABLED) {
			DisableMenuItem(((MacMenu *) menuBarPtr->entries[i]
				->childMenuRefPtr->menuPtr
				->platformData)->menuHdl, 0);
		    } else {
			EnableMenuItem(((MacMenu *) menuBarPtr->entries[i]
				->childMenuRefPtr->menuPtr
				->platformData)->menuHdl, 0);
		    }
		    continue;
		} else if (i == helpIndex) {
		    TkMenu *helpMenuPtr = menuBarPtr->entries[i]
			    ->childMenuRefPtr->menuPtr;

		    if (helpMenuPtr == NULL) {
			continue;
		    }
		    helpMenuPtr->menuFlags |= MENU_HELP_MENU;
		    if (!(helpMenuPtr->menuFlags
			    & MENU_RECONFIGURE_PENDING)) {
			helpMenuPtr->menuFlags
				|= MENU_RECONFIGURE_PENDING;
			Tcl_DoWhenIdle(ReconfigureMacintoshMenu,
				(ClientData) helpMenuPtr);
		    }
		    macMenuHdl =
			    ((MacMenu *) helpMenuPtr->platformData)->menuHdl;
		    currentHelpMenuID = GetMenuID(macMenuHdl);
		} else if (menuBarPtr->entries[i]->type
			== CASCADE_ENTRY) {
		    if ((menuBarPtr->entries[i]->childMenuRefPtr != NULL)
			    && menuBarPtr->entries[i]->childMenuRefPtr
			    ->menuPtr != NULL) {
			cascadeMenuPtr = menuBarPtr->entries[i]
				->childMenuRefPtr->menuPtr;
			macMenuHdl = ((MacMenu *) cascadeMenuPtr
				->platformData)->menuHdl;
			DeleteMenu(GetMenuID(macMenuHdl));
			InsertMenu(macMenuHdl, 0);
			RecursivelyInsertMenu(cascadeMenuPtr);
			if (menuBarPtr->entries[i]->state == ENTRY_DISABLED) {
			    DisableMenuItem(((MacMenu *) menuBarPtr->entries[i]
				    ->childMenuRefPtr->menuPtr
				    ->platformData)->menuHdl, 0);
			} else {
			    EnableMenuItem(((MacMenu *) menuBarPtr->entries[i]
				    ->childMenuRefPtr->menuPtr
				    ->platformData)->menuHdl, 0);
			 }
		    }
		}
	    }
	}
    }
    if (!menuRefPtr || !menuBarPtr) {
	SetDefaultMenubar();
    }
    DrawMenuBar();
    menuBarFlags &= ~MENUBAR_REDRAW_PENDING;
}

/*
 *----------------------------------------------------------------------
 *
 * RecursivelyInsertMenu --
 *
 *	Puts all of the cascades of this menu in the Mac hierarchical list.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The menubar is changed.
 *
 *----------------------------------------------------------------------
 */

void
RecursivelyInsertMenu(
    TkMenu *menuPtr)		/* All of the cascade items in this menu
				 * will be inserted into the mac menubar. */
{
    int i;
    TkMenu *cascadeMenuPtr;
    MenuHandle macMenuHdl;

    for (i = 0; i < menuPtr->numEntries; i++) {
	if (menuPtr->entries[i]->type == CASCADE_ENTRY) {
	    if ((menuPtr->entries[i]->childMenuRefPtr != NULL) &&
		    (menuPtr->entries[i]->childMenuRefPtr->menuPtr != NULL)) {
		cascadeMenuPtr = menuPtr->entries[i]->childMenuRefPtr->menuPtr;
		macMenuHdl =
			((MacMenu *) cascadeMenuPtr->platformData)->menuHdl;
		InsertMenu(macMenuHdl, -1);
		RecursivelyInsertMenu(cascadeMenuPtr);
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RecursivelyDeleteMenu --
 *
 *	Takes all of the cascades of this menu out of the Mac hierarchical
 *	list.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The menubar is changed.
 *
 *----------------------------------------------------------------------
 */

void
RecursivelyDeleteMenu(
    TkMenu *menuPtr)		/* All of the cascade items in this menu
				 * will be deleted from the mac menubar. */
{
    int i;
    TkMenu *cascadeMenuPtr;
    MenuHandle macMenuHdl;

    for (i = 0; i < menuPtr->numEntries; i++) {
	if (menuPtr->entries[i]->type == CASCADE_ENTRY) {
	    if ((menuPtr->entries[i]->childMenuRefPtr != NULL) &&
		    (menuPtr->entries[i]->childMenuRefPtr->menuPtr != NULL)) {
		cascadeMenuPtr = menuPtr->entries[i]->childMenuRefPtr->menuPtr;
		macMenuHdl =
			((MacMenu *) cascadeMenuPtr->platformData)->menuHdl;
		DeleteMenu(GetMenuID(macMenuHdl));
		RecursivelyDeleteMenu(cascadeMenuPtr);
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * SetDefaultMenubar --
 *
 *	Puts the Apple, File and Edit menus into the Macintosh menubar.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The menubar is changed.
 *
 *----------------------------------------------------------------------
 */

void
SetDefaultMenubar(void)
{
    if (currentMenuBarName != NULL) {
	ckfree(currentMenuBarName);
	currentMenuBarName = NULL;
    }
    currentMenuBarOwner = NULL;
    ClearMenuBar();
    InsertMenu(tkAppleMenu, 0);
    InsertMenu(tkFileMenu, 0);
    InsertMenu(tkEditMenu, 0);
    if (!(menuBarFlags & MENUBAR_REDRAW_PENDING)) {
	Tcl_DoWhenIdle(DrawMenuBarWhenIdle, NULL);
	menuBarFlags |= MENUBAR_REDRAW_PENDING;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkpSetMainMenubar --
 *
 *	Puts the menu associated with a window into the menubar. Should
 *	only be called when the window is in front.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The menubar is changed.
 *
 *----------------------------------------------------------------------
 */

void
TkpSetMainMenubar(
    Tcl_Interp *interp,		/* The interpreter of the application */
    Tk_Window tkwin,		/* The frame we are setting up */
    char *menuName)		/* The name of the menu to put in front.
				 * If NULL, use the default menu bar.
				 */
{
    TkWindow *winPtr = (TkWindow *) tkwin;
    WindowRef macWindowPtr;
    WindowRef frontNonFloating;

    macWindowPtr = TkMacOSXDrawableWindow(winPtr->window);

    frontNonFloating = ActiveNonFloatingWindow();
    if ((macWindowPtr == NULL) || (macWindowPtr != frontNonFloating)) {
	return;
    }

    if ((currentMenuBarInterp != interp) || (currentMenuBarOwner != tkwin)
	    || (currentMenuBarName == NULL) || (menuName == NULL)
	    || (strcmp(menuName, currentMenuBarName) != 0)) {
	Tk_Window searchWindow;
	TopLevelMenubarList *listPtr;

	if (currentMenuBarName != NULL) {
	    ckfree(currentMenuBarName);
	}

	if (menuName == NULL) {
	    searchWindow = tkwin;
	    if (strcmp(Tk_Class(searchWindow), "Menu") == 0) {
		TkMenuReferences *menuRefPtr;

		menuRefPtr = TkFindMenuReferences(interp, Tk_PathName(tkwin));
		if (menuRefPtr != NULL) {
		    TkMenu *menuPtr = menuRefPtr->menuPtr;

		    if (menuPtr != NULL) {
			searchWindow = menuPtr->masterMenuPtr->tkwin;
		    }
		}
	    }
	    for (; searchWindow != NULL;
		    searchWindow = Tk_Parent(searchWindow)) {
		for (listPtr = windowListPtr; listPtr != NULL;
			listPtr = listPtr->nextPtr) {
		    if (listPtr->tkwin == searchWindow) {
			break;
		    }
		}
		if (listPtr != NULL) {
		    menuName = Tk_PathName(
			    listPtr->menuPtr->masterMenuPtr->tkwin);
		    break;
		}
	    }
	}

	if (menuName == NULL) {
	    currentMenuBarName = NULL;
	} else {
	    currentMenuBarName = ckalloc(strlen(menuName) + 1);
	    strcpy(currentMenuBarName, menuName);
	}
	currentMenuBarOwner = tkwin;
	currentMenuBarInterp = interp;
    }
    if (!(menuBarFlags & MENUBAR_REDRAW_PENDING)) {
	Tcl_DoWhenIdle(DrawMenuBarWhenIdle, NULL);
	menuBarFlags |= MENUBAR_REDRAW_PENDING;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkpSetWindowMenuBar --
 *
 *	Associates a given menu with a window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	On Windows and UNIX, associates the platform menu with the
 *	platform window.
 *
 *----------------------------------------------------------------------
 */

void
TkpSetWindowMenuBar(
    Tk_Window tkwin,		/* The window we are setting the menu in */
    TkMenu *menuPtr)		/* The menu we are setting */
{
    TopLevelMenubarList *listPtr, *prevPtr;

    /*
     * Remove any existing reference to this window.
     */

    for (prevPtr = NULL, listPtr = windowListPtr;
	    listPtr != NULL;
	    prevPtr = listPtr, listPtr = listPtr->nextPtr) {
	if (listPtr->tkwin == tkwin) {
	    break;
	}
    }

    if (listPtr != NULL) {
	if (prevPtr != NULL) {
	    prevPtr->nextPtr = listPtr->nextPtr;
	} else {
	    windowListPtr = listPtr->nextPtr;
	}
	ckfree((char *) listPtr);
    }

    if (menuPtr != NULL) {
	listPtr = (TopLevelMenubarList *) ckalloc(sizeof(TopLevelMenubarList));
	listPtr->nextPtr = windowListPtr;
	windowListPtr = listPtr;
	listPtr->tkwin = tkwin;
	listPtr->menuPtr = menuPtr;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EventuallyInvokeMenu --
 *
 *	This IdleTime callback actually invokes the menu command
 *	scheduled in TkMacOSXDispatchMenuEvent.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands get executed.
 *
 *----------------------------------------------------------------------
 */

void
EventuallyInvokeMenu (
    ClientData data)
{
    struct MenuCommandHandlerData *realData =
	    (struct MenuCommandHandlerData *) data;
    int code;

    code = TkInvokeMenu(realData->menuPtr->interp, realData->menuPtr,
	    realData->index);

    if (code != TCL_OK && code != TCL_CONTINUE && code != TCL_BREAK) {
	Tcl_AddErrorInfo(realData->menuPtr->interp, "\n    (menu invoke)");
	Tcl_BackgroundError(realData->menuPtr->interp);
    }

    if (realData->menuPtr->tkwin) {
	RecursivelyClearActiveMenu(realData->menuPtr);
    }
    TkMacOSXClearMenubarActive();

    Tcl_Release(realData->menuPtr->interp);
    Tcl_Release(realData->menuPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXDispatchMenuEvent --
 *
 *	Given a menu id and an item, dispatches the command associated
 *	with it.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands for the event are scheduled for execution at idle time.
 *
 *----------------------------------------------------------------------
 */

int
TkMacOSXDispatchMenuEvent(
    int menuID,			/* The menu id of the menu we are invoking */
    int index)			/* The one-based index of the item that was
				 * selected. */
{
    int result = TCL_OK;

    if (menuID != 0) {
	if (menuID == kHMHelpMenuID) {
	    if (currentMenuBarOwner != NULL) {
		TkMenuReferences *helpMenuRef;
		char *helpMenuName = ckalloc(strlen(currentMenuBarName)
			+ strlen(".help") + 1);

		sprintf(helpMenuName, "%s.help", currentMenuBarName);
		helpMenuRef = TkFindMenuReferences(currentMenuBarInterp,
			helpMenuName);
		ckfree(helpMenuName);
		if ((helpMenuRef != NULL) && (helpMenuRef->menuPtr != NULL)) {
		    MenuRef outHelpMenu;
		    MenuItemIndex itemIndex;
		    int newIndex;

		    HMGetHelpMenu(&outHelpMenu, &itemIndex);
		    newIndex = index - itemIndex;
		    result = TkInvokeMenu(currentMenuBarInterp,
			    helpMenuRef->menuPtr, newIndex);
		}
	    }
	} else {
	    Tcl_HashEntry *commandEntryPtr =
		    Tcl_FindHashEntry(&commandTable, (char*)(intptr_t)menuID);
	    if (commandEntryPtr != NULL) {
		TkMenu *menuPtr = (TkMenu *) Tcl_GetHashValue(commandEntryPtr);

		if ((currentAppleMenuID == menuID)
			&& (index > menuPtr->numEntries + 1)) {
		    /*
		     * We don't need to do anything here, the standard
		     * Application event handler will open the built-in
		     * Apple menu item for us.
		     */
		    result = TCL_OK;
		} else {
		    struct MenuCommandHandlerData *data
			    = (struct MenuCommandHandlerData *)
			    ckalloc(sizeof(struct MenuCommandHandlerData));

		    Tcl_Preserve(menuPtr->interp);
		    Tcl_Preserve(menuPtr);
		    data->menuPtr = menuPtr;
		    data->index = index - 1;
		    Tcl_DoWhenIdle(EventuallyInvokeMenu,
			    (ClientData) data);
		    /* result = TkInvokeMenu(menuPtr->interp, menuPtr, index - 1); */
		}
	    } else {
		return TCL_ERROR;
	    }
	}
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * GetMenuIndicatorGeometry --
 *
 *	Gets the width and height of the indicator area of a menu.
 *
 * Results:
 *	widthPtr and heightPtr are set.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
GetMenuIndicatorGeometry (
    TkMenu *menuPtr,		/* The menu we are drawing */
    TkMenuEntry *mePtr,		/* The entry we are measuring */
    Tk_Font tkfont,		/* Precalculated font */
    const Tk_FontMetrics *fmPtr,/* Precalculated font metrics */
    int *widthPtr,		/* The resulting width */
    int *heightPtr)		/* The resulting height */
{
    *heightPtr = fmPtr->linespace + menuItemExtraHeight;
    if (IS_THEME_MENU_FONT(tkfont)) {
	*widthPtr = menuMarkColumnWidth;
    } else {
	const char markChar = FindMarkCharacter(mePtr);
	const char *markUtf = NULL;
	int len;

	len = GetUtfMarkCharacter(markChar, &markUtf);
	*widthPtr = Tk_TextWidth(tkfont, markUtf, len) + 2*menuMarkIndent;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetMenuAccelGeometry --
 *
 *	Gets the width and height of the accelerator area of a menu.
 *
 * Results:
 *	widthPtr and heightPtr are set.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
GetMenuAccelGeometry (
    TkMenu *menuPtr,		/* The menu we are measuring */
    TkMenuEntry *mePtr,		/* The entry we are measuring */
    Tk_Font tkfont,		/* The precalculated font */
    const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
    int *modWidthPtr,		/* The width of all of the key
				 * modifier symbols. */
    int *textWidthPtr,		/* The resulting width */
    int *heightPtr)		/* The resulting height */
{
    *heightPtr = fmPtr->linespace + menuItemExtraHeight;
    *modWidthPtr = menuSymbols[COMMAND_SYMBOL].width;
    *textWidthPtr = 0;
    if (mePtr->type != CASCADE_ENTRY && mePtr->accelLength > 0) {
	const char *accel = (mePtr->accelPtr == NULL) ? ""
		: Tcl_GetString(mePtr->accelPtr);
	EntryGeometry *geometryPtr = (EntryGeometry*)mePtr->platformEntryData;

	if (IS_THEME_MENU_FONT(tkfont)) {
	    CFStringRef cfStr;
	    int width = 0;
	    int maxWidth = ((TkFont *)tkfont)->fm.maxWidth;

	    if (geometryPtr->accelGlyph) {
		cfStr = CFStringCreateWithBytes(NULL,
			(UInt8*)&geometryPtr->accelGlyph, 1,
			kTextEncodingMacKeyboardGlyphs, false);
		if (cfStr) {
		    width = MeasureThemeText(cfStr, kThemeMenuItemCmdKeyFont);
		    CFRelease(cfStr);
		}
	    }
	    if ((mePtr->entryFlags & ENTRY_ACCEL_MASK) == 0) {
		if (!geometryPtr->accelGlyph) {
		     width = Tk_TextWidth(tkfont, accel, mePtr->accelLength);
		 }
		*textWidthPtr = maxWidth;
		if (width < maxWidth) {
		    *modWidthPtr = 0;
		} else {
		    *modWidthPtr = width - maxWidth;
		}
	    } else {
		if (!geometryPtr->accelGlyph) {
		    width = Tk_TextWidth(tkfont, accel +
			    geometryPtr->accelTextStart, mePtr->accelLength -
			    geometryPtr->accelTextStart);
		}
		if (width < maxWidth) {
		    *textWidthPtr = maxWidth;
		} else {
		    *textWidthPtr = width;
		}
		if (geometryPtr->modifierNum) {
		    *modWidthPtr = geometryPtr->modifierWidth;
		}
	    }
	} else {
	    *textWidthPtr = Tk_TextWidth(tkfont, accel, mePtr->accelLength);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetTearoffEntryGeometry --
 *
 *	Gets the width and height of of a tearoff entry.
 *
 * Results:
 *	widthPtr and heightPtr are set.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
GetTearoffEntryGeometry (
    TkMenu *menuPtr,		/* The menu we are drawing */
    TkMenuEntry *mePtr,		/* The entry we are measuring */
    Tk_Font tkfont,		/* The precalculated font */
    const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
    int *widthPtr,		/* The resulting width */
    int *heightPtr)		/* The resulting height */
{
#ifdef USE_TK_MDEF
    const int useMDEF = ((MacMenu *) menuPtr->platformData)->useMDEF;
#endif
    if (useMDEF && menuPtr->menuType != TEAROFF_MENU) {
	*heightPtr = fmPtr->linespace + menuItemExtraHeight;
	*widthPtr = menuPtr->totalWidth;
    } else {
	*widthPtr = *heightPtr = 0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetMenuSeparatorGeometry --
 *
 *	Gets the width and height of menu separator.
 *
 * Results:
 *	widthPtr and heightPtr are set.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
GetMenuSeparatorGeometry(
    TkMenu *menuPtr,		/* The menu we are drawing */
    TkMenuEntry *mePtr,		/* The entry we are measuring */
    Tk_Font tkfont,		/* The precalculated font */
    const Tk_FontMetrics *fmPtr,/* The precalcualted font metrics */
    int *widthPtr,		/* The resulting width */
    int *heightPtr)		/* The resulting height */
{
    *widthPtr = 0;
    *heightPtr = menuSeparatorHeight;
}

/*
 *----------------------------------------------------------------------
 *
 * DrawMenuEntryIndicator --
 *
 *	This procedure draws the indicator part of a menu.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands are output to X to display the menu in its
 *	current mode.
 *
 *----------------------------------------------------------------------
 */

void
DrawMenuEntryIndicator(
    TkMenu *menuPtr,		/* The menu we are drawing */
    TkMenuEntry *mePtr,		/* The entry we are drawing */
    Drawable d,			/* The drawable we are drawing */
    GC gc,			/* The GC we are drawing with */
    GC indicatorGC,		/* The GC to use for the indicator */
    Tk_Font tkfont,		/* The precalculated font */
    const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
    int x,			/* topleft hand corner of entry */
    int y,			/* topleft hand corner of entry */
    int width,			/* width of entry */
    int height)			/* height of entry */
{
    if ((mePtr->type == CHECK_BUTTON_ENTRY) ||
	    (mePtr->type == RADIO_BUTTON_ENTRY)) {
	if (mePtr->indicatorOn && (mePtr->entryFlags & ENTRY_SELECTED)) {
	    short mark;
	    int baseline = y + (height + fmPtr->ascent - fmPtr->descent)/2;

	    GetItemMark(((MacMenu *) menuPtr->platformData)->menuHdl,
		    mePtr->index + 1, &mark);
	    if (IS_THEME_MENU_FONT(tkfont)) {
		ThemeFontID font = kThemeMenuItemMarkFont;
		TextEncoding encoding = GetApplicationTextEncoding();
		CFStringRef cfStr;
		ThemeDrawState drawState;
		Rect bounds = {y, x + menuMarkIndent, y + height, x + width};

		if (mark < kSpaceCharCode) {
		    font = kThemeMenuItemCmdKeyFont;
		    encoding = kTextEncodingMacKeyboardGlyphs;
		}
		switch (mePtr->state) {
		    case ENTRY_ACTIVE:
			drawState = kThemeStatePressed;
			break;
		    case ENTRY_DISABLED:
			drawState = kThemeStateInactive;
			break;
		    default:
			drawState = kThemeStateActive;
			break;
		}
		cfStr = CFStringCreateWithBytes(NULL, (UInt8*)&mark, 1,
			encoding, false);
		if (cfStr) {
		    DrawThemeText(d, gc, cfStr, font, drawState, &bounds,
			    baseline, teFlushDefault);
		    CFRelease(cfStr);
		}
	    } else if (mark != 0) {
		const char *markUtf = NULL;
		int len;

		len = GetUtfMarkCharacter(mark, &markUtf);
		Tk_DrawChars(menuPtr->display, d, gc, tkfont, markUtf, len,
		    x + menuMarkIndent, baseline);
	    }
	}
    }
}

#ifdef USE_TK_MDEF
/*
 *----------------------------------------------------------------------
 *
 * DrawMenuBackground --
 *
 *	If Appearance is present, draws the Appearance background
 *
 * Results:
 *	Nothing
 *
 * Side effects:
 *	Commands are output to X to display the menu in its
 *	current mode.
 *
 *----------------------------------------------------------------------
 */
void
DrawMenuBackground(
    TkMenu *menuPtr,
    Rect     *menuRectPtr,	/* The menu rect */
    Drawable d)			/* What we are drawing into */
{
    Tk_3DBorder border;

    EraseMenuBackground(((MacMenu *) menuPtr->platformData)->menuHdl,
	    menuRectPtr, ((MacDrawable*)d)->context);
    border = Tk_Get3DBorderFromObj(menuPtr->tkwin, menuPtr->borderPtr);
    Tk_Fill3DRectangle(menuPtr->tkwin, d, border,
	    menuRectPtr->left, menuRectPtr->top,
	    menuRectPtr->right - menuRectPtr->left,
	    menuRectPtr->bottom - menuRectPtr->top, 0, TK_RELIEF_FLAT);
}
#endif /* USE_TK_MDEF */

/*
 *----------------------------------------------------------------------
 *
 * DrawMenuEntryAccelerator --
 *
 *	This procedure draws the accelerator part of a menu.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands are output to X to display the menu in its
 *	current mode.
 *
 *----------------------------------------------------------------------
 */

void
DrawMenuEntryAccelerator(
    TkMenu *menuPtr,		/* The menu we are drawing */
    TkMenuEntry *mePtr,		/* The entry we are drawing */
    Drawable d,			/* The drawable we are drawing in */
    GC gc,			/* The gc to draw into */
    Tk_Font tkfont,		/* The precalculated font */
    const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
    Tk_3DBorder activeBorder,	/* border for menu background */
    int x,			/* The left side of the entry */
    int y,			/* The top of the entry */
    int width,			/* The width of the entry */
    int height,			/* The height of the entry */
    int drawArrow)		/* Whether or not to draw cascade arrow */
{
    if (mePtr->type != CASCADE_ENTRY && mePtr->accelLength > 0) {
	const char *accel = (mePtr->accelPtr == NULL) ? ""
		: Tcl_GetString(mePtr->accelPtr);
	EntryGeometry *geometryPtr = (EntryGeometry*)mePtr->platformEntryData;
	int leftEdge = x + width - geometryPtr->accelTextWidth;
	int baseline = y + (height + fmPtr->ascent - fmPtr->descent) / 2;

	if (IS_THEME_MENU_FONT(tkfont)) {
	    CFStringRef cfStr;
	    ThemeDrawState drawState;

	    switch (mePtr->state) {
		case ENTRY_ACTIVE:
		    drawState = kThemeStatePressed;
		    break;
		case ENTRY_DISABLED:
		    drawState = kThemeStateInactive;
		    break;
		default:
		    drawState = kThemeStateActive;
		    break;
	    }
	    if ((mePtr->entryFlags & ENTRY_ACCEL_MASK) == 0) {
		leftEdge -= geometryPtr->modifierWidth;
	    }
	    if (geometryPtr->accelGlyph) {
		Rect bounds = {y, leftEdge, y + height, leftEdge +
			geometryPtr->accelTextWidth};

		cfStr = CFStringCreateWithBytes(NULL,
			(UInt8*)&geometryPtr->accelGlyph, 1,
			kTextEncodingMacKeyboardGlyphs, false);
		if (cfStr) {
		    DrawThemeText(d, gc, cfStr, kThemeMenuItemCmdKeyFont,
			    drawState, &bounds, baseline, teFlushDefault);
		    CFRelease(cfStr);
		}
	    } else {
		Tk_DrawChars(menuPtr->display, d, gc, tkfont, accel + 
			geometryPtr->accelTextStart, mePtr->accelLength -
			geometryPtr->accelTextStart, leftEdge, baseline);
	    }
	    if (geometryPtr->modifierNum) {
		Rect bounds = {y, leftEdge - geometryPtr->modifierWidth,
			y + height, leftEdge};

		cfStr = CFStringCreateWithCharacters(NULL,
			geometryPtr->modifierUniChars,
			geometryPtr->modifierNum);
		if (cfStr) {
		    DrawThemeText(d, gc, cfStr, kThemeMenuItemCmdKeyFont,
			    drawState, &bounds, baseline, teFlushDefault);
		    CFRelease(cfStr);
		}
	    }
	} else {
	    Tk_DrawChars(menuPtr->display, d, gc, tkfont, accel,
		    mePtr->accelLength, leftEdge, baseline);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DrawMenuSeparator --
 *
 *	The menu separator is drawn.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands are output to X to display the menu in its
 *	current mode.
 *
 *----------------------------------------------------------------------
 */

void
DrawMenuSeparator(
    TkMenu *menuPtr,		/* The menu we are drawing */
    TkMenuEntry *mePtr,		/* The entry we are drawing */
    Drawable d,			/* The drawable we are drawing into */
    GC gc,			/* The gc we are drawing with */
    Tk_Font tkfont,		/* The precalculated font */
    const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
    int x,			/* left coordinate of entry */
    int y,			/* top coordinate of entry */
    int width,			/* width of entry */
    int height)			/* height of entry */
{
    TkMacOSXDrawingContext dc;
    Rect r;

    r.top = y;
    r.left = x;
    r.bottom = y + height;
    r.right = x + width;
    if (TkMacOSXSetupDrawingContext(d, gc, 1, &dc)) {
	ChkErr(DrawThemeMenuSeparator, &r);
	TkMacOSXRestoreDrawingContext(&dc);
    }
}

#ifdef USE_TK_MDEF
/*
 *----------------------------------------------------------------------
 *
 * AppearanceEntryDrawWrapper --
 *
 *	It routes to the Appearance Managers DrawThemeEntry, which will
 *	then call us back after setting up the drawing context.
 *
 * Results:
 *	A menu entry is drawn
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */
void
AppearanceEntryDrawWrapper(
    TkMenuEntry *mePtr,
    Rect *menuRectPtr,
    MenuTrackingData *mtdPtr,
    Drawable d,
    Tk_FontMetrics *fmPtr,
    Tk_Font tkfont,
    int erase)
{
    MenuEntryUserData meData;
    Rect itemRect;
    ThemeMenuState theState;
    ThemeMenuItemType theType;
    Tk_FontMetrics entryMetrics;

    meData.mePtr = mePtr;
    meData.mdefDrawable = d;
    if (mePtr->fontPtr == NULL) {
	meData.fmPtr = fmPtr;
	meData.tkfont = tkfont;
    } else {
	meData.tkfont = Tk_GetFontFromObj(mePtr->menuPtr->tkwin,
		mePtr->fontPtr);
	Tk_GetFontMetrics(meData.tkfont, &entryMetrics);
	fmPtr = &entryMetrics;
    }
    itemRect.left = menuRectPtr->left + mePtr->x;
    itemRect.top = mtdPtr->virtualMenuTop + mePtr->y;
    itemRect.right = mePtr->entryFlags & ENTRY_LAST_COLUMN ?
	    menuRectPtr->right : itemRect.left + mePtr->width;
    itemRect.bottom = itemRect.top + mePtr->height;

    if (mePtr->state == ENTRY_ACTIVE) {
	theState = kThemeMenuSelected;
    } else if (mePtr->state == ENTRY_DISABLED) {
	theState = kThemeMenuDisabled;
    } else {
	theState = kThemeMenuActive;
    }
    if (mePtr->type == CASCADE_ENTRY) {
	theType = kThemeMenuItemHierarchical;
    } else {
	theType = kThemeMenuItemPlain;
    }
    if (erase) {
	DisableScreenUpdates();
	DrawMenuBackground(mePtr->menuPtr, &itemRect, d);
    }
    DrawThemeMenuItem(menuRectPtr, &itemRect,
	mtdPtr->virtualMenuTop, mtdPtr->virtualMenuBottom, theState,
	theType | kThemeMenuItemNoBackground, tkThemeMenuItemDrawingUPP,
	(unsigned long) &meData);
    if (erase) {
	EnableScreenUpdates();
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ThemeMenuItemDrawingProc --
 *
 *	This routine is called from the Appearance DrawThemeMenuEntry
 *
 * Results:
 *	A menu entry is drawn
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */
pascal void
ThemeMenuItemDrawingProc(
    const Rect *inBounds,
    SInt16 inDepth,
    Boolean inIsColorDevice,
    SInt32 inUserData)
{
    MenuEntryUserData *meData = (MenuEntryUserData *) inUserData;

    TkpDrawMenuEntry(meData->mePtr, meData->mdefDrawable, meData->tkfont,
	    meData->fmPtr, inBounds->left, inBounds->top, inBounds->right -
	    inBounds->left + menuItemExtraWidth, inBounds->bottom -
	    inBounds->top + menuItemExtraHeight, 0, 1);
}
#endif /* USE_TK_MDEF */

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXHandleTearoffMenu() --
 *
 *	This routine sees if the MDEF has set a menu and a mouse position
 *	for tearing off and makes a tearoff menu if it has.
 *
 * Results:
 *	menuPtr->interp will have the result of the tearoff command.
 *
 * Side effects:
 *	A new tearoff menu is created if it is supposed to be.
 *
 *----------------------------------------------------------------------
 */

void
TkMacOSXHandleTearoffMenu(void)
{
    /*
     * Obsolete: Nothing to do.
     */
}

/*
 *--------------------------------------------------------------
 *
 * TkpInitializeMenuBindings --
 *
 *	For every interp, initializes the bindings for Windows
 *	menus. Does nothing on Mac or XWindows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	C-level bindings are setup for the interp which will
 *	handle Alt-key sequences for menus without beeping
 *	or interfering with user-defined Alt-key bindings.
 *
 *--------------------------------------------------------------
 */

void
TkpInitializeMenuBindings(
    Tcl_Interp *interp,		/* The interpreter to set. */
    Tk_BindingTable bindingTable)
				/* The table to add to. */
{
    /*
     * Nothing to do.
     */
}

/*
 *--------------------------------------------------------------
 *
 * TkpComputeMenubarGeometry --
 *
 *	This procedure is invoked to recompute the size and
 *	layout of a menu that is a menubar clone.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Fields of menu entries are changed to reflect their
 *	current positions, and the size of the menu window
 *	itself may be changed.
 *
 *--------------------------------------------------------------
 */

void
TkpComputeMenubarGeometry(
    TkMenu *menuPtr)		/* Structure describing menu. */
{
    TkpComputeStandardMenuGeometry(menuPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * DrawTearoffEntry --
 *
 *	This procedure draws a tearoff entry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands are output to X to display the menu in its
 *	current mode.
 *
 *----------------------------------------------------------------------
 */

void
DrawTearoffEntry(
    TkMenu *menuPtr,		/* The menu we are drawing */
    TkMenuEntry *mePtr,		/* The entry we are drawing */
    Drawable d,			/* The drawable we are drawing into */
    GC gc,			/* The gc we are drawing with */
    Tk_Font tkfont,		/* The font we are drawing with */
    const Tk_FontMetrics *fmPtr,/* The metrics we are drawing with */
    int x,			/* Left edge of entry. */
    int y,			/* Top edge of entry. */
    int width,			/* Width of entry. */
    int height)			/* Height of entry. */
{
    XPoint points[2];
    int margin, segmentWidth, maxX;
    Tk_3DBorder border;

    if (menuPtr->menuType != MASTER_MENU ) {
	return;
    }

    margin = fmPtr->linespace/2;
    points[0].x = x;
    points[0].y = y + height/2;
    points[1].y = points[0].y;
    segmentWidth = 6;
    maxX  = x + menuPtr->totalWidth - 1;
    border = Tk_Get3DBorderFromObj(menuPtr->tkwin, menuPtr->borderPtr);

    while (points[0].x < maxX) {
	points[1].x = points[0].x + segmentWidth;
	if (points[1].x > maxX) {
	    points[1].x = maxX;
	}
	Tk_Draw3DPolygon(menuPtr->tkwin, d, border, points, 2, 1,
		TK_RELIEF_RAISED);
	points[0].x += 2*segmentWidth;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXSetHelpMenuItemCount --
 *
 *	Has to be called after the first call to InsertMenu. Sets
 *	up the global variable for the number of items in the
 *	unmodified help menu.
 *	NB. Nobody uses this any more, since you can get the number
 *	of system help items from HMGetHelpMenu trivially.
 *	But it is in the stubs table...
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Nothing.
 *
 *----------------------------------------------------------------------
 */

void
TkMacOSXSetHelpMenuItemCount(void)
{
    /*
     * Obsolete: Nothing to do.
     */
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXMenuClick --
 *
 *	Prepares a menubar for MenuSelect or MenuKey.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Any pending configurations of the menubar are completed.
 *
 *----------------------------------------------------------------------
 */

void
TkMacOSXMenuClick(void)
{
    TkMenu *menuPtr;
    TkMenuReferences *menuRefPtr;

    if ((currentMenuBarInterp != NULL) && (currentMenuBarName != NULL)) {
	menuRefPtr = TkFindMenuReferences(currentMenuBarInterp,
		currentMenuBarName);
	for (menuPtr = menuRefPtr->menuPtr->masterMenuPtr;
		menuPtr != NULL; menuPtr = menuPtr->nextInstancePtr) {
	    if (menuPtr->menuType == MENUBAR) {
		CompleteIdlers(menuPtr);
		break;
	    }
	}
    }

    if (menuBarFlags & MENUBAR_REDRAW_PENDING) {
	Tcl_CancelIdleCall(DrawMenuBarWhenIdle, NULL);
	DrawMenuBarWhenIdle(NULL);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkpDrawMenuEntry --
 *
 *	Draws the given menu entry at the given coordinates with the
 *	given attributes.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	X Server commands are executed to display the menu entry.
 *
 *----------------------------------------------------------------------
 */

void
TkpDrawMenuEntry(
    TkMenuEntry *mePtr,		/* The entry to draw */
    Drawable d,			/* What to draw into */
    Tk_Font tkfont,		/* Precalculated font for menu */
    const Tk_FontMetrics *menuMetricsPtr,
				/* Precalculated metrics for menu */
    int x,			/* X-coordinate of topleft of entry */
    int y,			/* Y-coordinate of topleft of entry */
    int width,			/* Width of the entry rectangle */
    int height,			/* Height of the current rectangle */
    int strictMotif,		/* Boolean flag */
    int drawArrow)		/* Whether or not to draw the cascade
				 * arrow for cascade items. Only applies
				 * to Windows. */
{
    GC gc;
    TkMenu *menuPtr = mePtr->menuPtr;
    int padY = (menuPtr->menuType == MENUBAR) ? 3 : 0;
    GC indicatorGC;
    Tk_3DBorder bgBorder, activeBorder;
    const Tk_FontMetrics *fmPtr;
    Tk_FontMetrics entryMetrics;
    int adjustedY = y + padY;
    int adjustedHeight = height - 2 * padY;

    /*
     * Choose the gc for drawing the foreground part of the entry.
     * Under Appearance, we pass a null (appearanceGC) to tell
     * ourselves not to change whatever color the appearance manager has set.
     */

    if ((mePtr->state == ENTRY_ACTIVE) && !strictMotif) {
	gc = mePtr->activeGC;
	if (gc == NULL) {
	    gc = menuPtr->activeGC;
	}
    } else {
	TkMenuEntry *parentEntryPtr = GetParentMenuEntry(menuPtr);

	if (((parentEntryPtr && parentEntryPtr->state == ENTRY_DISABLED) ||
		(mePtr->state == ENTRY_DISABLED)) &&
		(menuPtr->disabledFgPtr != NULL)) {
	    gc = mePtr->disabledGC;
	    if (gc == NULL) {
		gc = menuPtr->disabledGC;
	    }
	} else {
	    gc = mePtr->textGC;
	    if (gc == NULL) {
		gc = menuPtr->textGC;
	    }
	}
    }

    indicatorGC = mePtr->indicatorGC;
    if (indicatorGC == NULL) {
	indicatorGC = menuPtr->indicatorGC;
    }

    bgBorder = Tk_Get3DBorderFromObj(menuPtr->tkwin,
	    (mePtr->borderPtr == NULL)
	    ? menuPtr->borderPtr : mePtr->borderPtr);
    if (strictMotif) {
	activeBorder = bgBorder;
    } else {
	activeBorder = Tk_Get3DBorderFromObj(menuPtr->tkwin,
	    (mePtr->activeBorderPtr == NULL)
	    ? menuPtr->activeBorderPtr : mePtr->activeBorderPtr);
    }

    if (mePtr->fontPtr == NULL) {
	fmPtr = menuMetricsPtr;
    } else {
	tkfont = Tk_GetFontFromObj(menuPtr->tkwin, mePtr->fontPtr);
	Tk_GetFontMetrics(tkfont, &entryMetrics);
	fmPtr = &entryMetrics;
    }

    /*
     * Need to draw the entire background, including padding. On Unix,
     * for menubars, we have to draw the rest of the entry taking
     * into account the padding.
     */

    DrawMenuEntryBackground(menuPtr, mePtr, d, activeBorder, bgBorder, x, y,
	    width, height);

    if (mePtr->type == SEPARATOR_ENTRY) {
	DrawMenuSeparator(menuPtr, mePtr, d, gc, tkfont,
		fmPtr, x, adjustedY, width, adjustedHeight);
    } else if (mePtr->type == TEAROFF_ENTRY) {
	DrawTearoffEntry(menuPtr, mePtr, d, gc, tkfont, fmPtr, x, adjustedY,
		width, adjustedHeight);
    } else {
	DrawMenuEntryLabel(menuPtr, mePtr, d, gc, tkfont, fmPtr, x,
		adjustedY, width, adjustedHeight);
	DrawMenuEntryAccelerator(menuPtr, mePtr, d, gc, tkfont, fmPtr,
		activeBorder, x, adjustedY, width, adjustedHeight, drawArrow);
	if (!mePtr->hideMargin) {
	    DrawMenuEntryIndicator(menuPtr, mePtr, d, gc, indicatorGC, tkfont,
		    fmPtr, x, adjustedY, width, adjustedHeight);
	}
    }
}

/*
 *--------------------------------------------------------------
 *
 * TkpComputeStandardMenuGeometry --
 *
 *	This procedure is invoked to recompute the size and
 *	layout of a menu that is not a menubar clone.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Fields of menu entries are changed to reflect their
 *	current positions, and the size of the menu window
 *	itself may be changed.
 *
 *--------------------------------------------------------------
 */

void
TkpComputeStandardMenuGeometry(
    TkMenu *menuPtr)		/* Structure describing menu. */
{
    Tk_Font tkfont, menuFont;
    Tk_FontMetrics menuMetrics, entryMetrics, *fmPtr;
    int x, y, height, modifierWidth, labelWidth, indicatorSpace;
    int windowWidth, windowHeight, accelWidth, maxAccelTextWidth;
    int i, j, lastColumnBreak, maxModifierWidth, maxWidth, nonAccelMargin;
    int maxNonAccelMargin, maxEntryWithAccelWidth, maxEntryWithoutAccelWidth;
    int entryWidth, maxIndicatorSpace, borderWidth, activeBorderWidth;
    TkMenuEntry *mePtr, *columnEntryPtr;
    EntryGeometry *geometryPtr;
    int haveAccel = 0;

    if (menuPtr->tkwin == NULL) {
	return;
    }

    Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->borderWidthPtr,
	    &borderWidth);
    Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->activeBorderWidthPtr,
	    &activeBorderWidth);
    x = y = borderWidth;
    indicatorSpace = labelWidth = accelWidth = maxAccelTextWidth = 0;
    windowHeight = windowWidth = maxWidth = lastColumnBreak = 0;
    maxModifierWidth = nonAccelMargin = maxNonAccelMargin = 0;
    maxEntryWithAccelWidth = maxEntryWithoutAccelWidth = 0;
    maxIndicatorSpace = 0;

    /*
     * On the Mac especially, getting font metrics can be quite slow,
     * so we want to do it intelligently. We are going to precalculate
     * them and pass them down to all of the measuring and drawing
     * routines. We will measure the font metrics of the menu once.
     * If an entry does not have its own font set, then we give
     * the geometry/drawing routines the menu's font and metrics.
     * If an entry has its own font, we will measure that font and
     * give all of the geometry/drawing the entry's font and metrics.
     */

    menuFont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr);
    Tk_GetFontMetrics(menuFont, &menuMetrics);

    for (i = 0; i < menuPtr->numEntries; i++) {
	mePtr = menuPtr->entries[i];
	if (mePtr->type == CASCADE_ENTRY || mePtr->accelLength > 0) {
	    haveAccel = 1;
	    break;
	}
    }

    for (i = 0; i < menuPtr->numEntries; i++) {
	mePtr = menuPtr->entries[i];
	if (mePtr->fontPtr == NULL) {
	    tkfont = menuFont;
	    fmPtr = &menuMetrics;
	} else {
	    tkfont = Tk_GetFontFromObj(menuPtr->tkwin, mePtr->fontPtr);
	    Tk_GetFontMetrics(tkfont, &entryMetrics);
	    fmPtr = &entryMetrics;
	}

	if ((i > 0) && mePtr->columnBreak) {
	    if (maxIndicatorSpace != 0) {
		maxIndicatorSpace += 2;
	    }
	    for (j = lastColumnBreak; j < i; j++) {
		columnEntryPtr = menuPtr->entries[j];
		geometryPtr =
			(EntryGeometry *) columnEntryPtr->platformEntryData;

		columnEntryPtr->indicatorSpace = maxIndicatorSpace;
		columnEntryPtr->width = maxIndicatorSpace + maxWidth
			+ 2 * activeBorderWidth;
		geometryPtr->accelTextWidth = maxAccelTextWidth;
		geometryPtr->modifierWidth = maxModifierWidth;
		columnEntryPtr->x = x;
		columnEntryPtr->entryFlags &= ~ENTRY_LAST_COLUMN;
		if (maxEntryWithoutAccelWidth > maxEntryWithAccelWidth) {
		    geometryPtr->nonAccelMargin = maxEntryWithoutAccelWidth
			    - maxEntryWithAccelWidth;
		    if (geometryPtr->nonAccelMargin > maxNonAccelMargin) {
			geometryPtr->nonAccelMargin = maxNonAccelMargin;
		    }
		} else {
		    geometryPtr->nonAccelMargin = 0;
		}
	    }
	    x += maxIndicatorSpace + maxWidth + 2 * borderWidth;
	    windowWidth = x;
	    maxWidth = maxIndicatorSpace = maxAccelTextWidth = 0;
	    maxModifierWidth = maxNonAccelMargin = maxEntryWithAccelWidth = 0;
	    maxEntryWithoutAccelWidth = 0;
	    lastColumnBreak = i;
	    y = borderWidth;
	}
	geometryPtr = (EntryGeometry *) mePtr->platformEntryData;

	if (mePtr->type == SEPARATOR_ENTRY) {
	    GetMenuSeparatorGeometry(menuPtr, mePtr, tkfont,
		    fmPtr, &entryWidth, &height);
	    mePtr->height = height;
	} else if (mePtr->type == TEAROFF_ENTRY) {
	    GetTearoffEntryGeometry(menuPtr, mePtr, tkfont,
		    fmPtr, &entryWidth, &height);
	    mePtr->height = height;
	} else {
	    /*
	     * For each entry, compute the height required by that
	     * particular entry, plus three widths:  the width of the
	     * label, the width to allow for an indicator to be displayed
	     * to the left of the label (if any), and the width of the
	     * accelerator to be displayed to the right of the label
	     * (if any). These sizes depend, of course, on the type
	     * of the entry.
	     */

	    GetMenuLabelGeometry(mePtr, tkfont, fmPtr, &labelWidth, &height);
	    mePtr->height = height;

	    nonAccelMargin = 0;
	    if (mePtr->type == CASCADE_ENTRY) {
		GetMenuAccelGeometry(menuPtr, mePtr, tkfont, fmPtr,
			&modifierWidth, &accelWidth, &height);
	    } else if (mePtr->accelLength == 0) {
		if (haveAccel && !mePtr->hideMargin) {
		    if (IS_THEME_MENU_FONT(tkfont)) {
			nonAccelMargin = menuSymbols[COMMAND_SYMBOL].width;
		    } else {
			nonAccelMargin = Tk_TextWidth(tkfont,
				menuSymbols[COMMAND_SYMBOL].utf,
				menuSymbols[COMMAND_SYMBOL].utfLen);
		    }
		}
		accelWidth = modifierWidth = 0;
	    } else {
		GetMenuAccelGeometry(menuPtr, mePtr, tkfont,
			fmPtr, &modifierWidth, &accelWidth, &height);
		if (height > mePtr->height) {
		    mePtr->height = height;
		}
	    }

	    if (!(mePtr->hideMargin)) {
		GetMenuIndicatorGeometry(menuPtr, mePtr, tkfont,
			fmPtr, &indicatorSpace, &height);
		if (height > mePtr->height) {
		    mePtr->height = height;
		}
	    } else {
		indicatorSpace = 0;
	    }

	    if (nonAccelMargin > maxNonAccelMargin) {
		maxNonAccelMargin = nonAccelMargin;
	    }
	    if (accelWidth > maxAccelTextWidth) {
		maxAccelTextWidth = accelWidth;
	    }
	    if (modifierWidth > maxModifierWidth) {
		maxModifierWidth = modifierWidth;
	    }
	    if (indicatorSpace > maxIndicatorSpace) {
		maxIndicatorSpace = indicatorSpace;
	    }

	    entryWidth = labelWidth + modifierWidth + accelWidth
		    + nonAccelMargin;

	    if (entryWidth > maxWidth) {
		maxWidth = entryWidth;
	    }

	    if (mePtr->accelLength > 0) {
		if (entryWidth > maxEntryWithAccelWidth) {
		    maxEntryWithAccelWidth = entryWidth;
		}
	    } else {
		if (entryWidth > maxEntryWithoutAccelWidth) {
		    maxEntryWithoutAccelWidth = entryWidth;
		}
	    }
	    mePtr->height += 2 * activeBorderWidth;
	}
	mePtr->y = y;
	y += menuPtr->entries[i]->height + borderWidth;
	if (y > windowHeight) {
	    windowHeight = y;
	}
    }

    for (j = lastColumnBreak; j < menuPtr->numEntries; j++) {
	columnEntryPtr = menuPtr->entries[j];
	geometryPtr = (EntryGeometry *) columnEntryPtr->platformEntryData;

	columnEntryPtr->indicatorSpace = maxIndicatorSpace;
	columnEntryPtr->width = maxIndicatorSpace + maxWidth
		+ 2 * activeBorderWidth;
	geometryPtr->accelTextWidth = maxAccelTextWidth;
	columnEntryPtr->x = x;
	columnEntryPtr->entryFlags |= ENTRY_LAST_COLUMN;
	if (maxEntryWithoutAccelWidth > maxEntryWithAccelWidth) {
	    geometryPtr->nonAccelMargin = maxEntryWithoutAccelWidth
		    - maxEntryWithAccelWidth;
	    if (geometryPtr->nonAccelMargin > maxNonAccelMargin) {
		geometryPtr->nonAccelMargin = maxNonAccelMargin;
	    }
	} else {
	    geometryPtr->nonAccelMargin = 0;
	}
    }
    windowWidth = x + maxIndicatorSpace + maxWidth
	    + 2 * activeBorderWidth + borderWidth;
    windowHeight += borderWidth;

    /*
     * The X server doesn't like zero dimensions, so round up to at least
     * 1 (a zero-sized menu should never really occur, anyway).
     */

    if (windowWidth <= 0) {
	windowWidth = 1;
    }
    if (windowHeight <= 0) {
	windowHeight = 1;
    }
    menuPtr->totalWidth = windowWidth;
    menuPtr->totalHeight = windowHeight;
}

/*
 *----------------------------------------------------------------------
 *
 * DrawMenuEntryLabel --
 *
 *	This procedure draws the label part of a menu.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands are output to X to display the menu in its
 *	current mode.
 *
 *----------------------------------------------------------------------
 */

void
DrawMenuEntryLabel(
    TkMenu *menuPtr,		/* The menu we are drawing */
    TkMenuEntry *mePtr,		/* The entry we are drawing */
    Drawable d,			/* What we are drawing into */
    GC gc,			/* The gc we are drawing into */
    Tk_Font tkfont,		/* The precalculated font */
    const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
    int x,			/* left edge */
    int y,			/* right edge */
    int width,			/* width of entry */
    int height)			/* height of entry */
{
    int imageWidth, imageHeight, textWidth = 0, textHeight = 0;
    int indicatorSpace =  mePtr->indicatorSpace;
    int leftEdge = x + indicatorSpace;
    int haveImage = 0, haveText = 0;
    int imageXOffset = 0, imageYOffset = 0;
    int textXOffset = 0, textYOffset = 0;
    Pixmap bitmap = (Pixmap) NULL;
    Tcl_DString itemTextDString;

    /*
     * Work out what we will need to draw first.
     */

    if (mePtr->image != NULL) {
	Tk_SizeOfImage(mePtr->image, &imageWidth, &imageHeight);
	haveImage = 1;
    } else if (mePtr->bitmapPtr != NULL) {
	bitmap = Tk_GetBitmapFromObj(menuPtr->tkwin, mePtr->bitmapPtr);
	Tk_SizeOfBitmap(menuPtr->display, bitmap, &imageWidth, &imageHeight);
	haveImage = 1;
    }
    if (!haveImage || (mePtr->compound != COMPOUND_NONE)) {
	if (mePtr->labelLength > 0) {
	    GetEntryText(mePtr, &itemTextDString);
	    if (mePtr->compound != COMPOUND_NONE) {
		textWidth = Tk_TextWidth(tkfont,
			Tcl_DStringValue(&itemTextDString),
			Tcl_DStringLength(&itemTextDString)) +
			menuTextLeadingEdgeMargin + menuTextTrailingEdgeMargin;
		textHeight = fmPtr->linespace;
	    }
	    haveText = 1;
	}
    }

    /*
     * Now work out what the relative positions are.
     */

    if (haveImage && haveText && (mePtr->compound != COMPOUND_NONE)) {
	int fullWidth = (imageWidth > textWidth ? imageWidth : textWidth);

	switch ((enum compound) mePtr->compound) {
	    case COMPOUND_TOP:
		textXOffset = (fullWidth - textWidth)/2;
		textYOffset = imageHeight/2 + 2;
		imageXOffset = (fullWidth - imageWidth)/2;
		imageYOffset = -textHeight/2;
		break;
	    case COMPOUND_BOTTOM:
		textXOffset = (fullWidth - textWidth)/2;
		textYOffset = -imageHeight/2;
		imageXOffset = (fullWidth - imageWidth)/2;
		imageYOffset = textHeight/2 + 2;
		break;
	    case COMPOUND_LEFT:
		/*
		 * Position image in the indicator space to the left of the
		 * entries, unless this entry is a radio|check button because
		 * then the indicator space will be used.
		 */

		textXOffset = imageWidth + 2 - menuTextLeadingEdgeMargin;
		if ((mePtr->type != CHECK_BUTTON_ENTRY)
			&& (mePtr->type != RADIO_BUTTON_ENTRY)) {
		    textXOffset -= indicatorSpace;
		    imageXOffset = -indicatorSpace;
		}
		if (textXOffset < 0) {
		    textXOffset = 0;
		}
		break;
	    case COMPOUND_RIGHT:
		imageXOffset = textWidth + 2 - menuTextTrailingEdgeMargin;
		break;
	    case COMPOUND_CENTER:
		textXOffset = (fullWidth - textWidth)/2;
		imageXOffset = (fullWidth - imageWidth)/2;
		break;
	    case COMPOUND_NONE:
	    	/*
	    	 * Never reached.
	    	 */
		break;
	}
    }

    /*
     * Draw label and/or bitmap or image for entry.
     */

    if (mePtr->image != NULL) {
	if ((mePtr->selectImage != NULL)
		&& (mePtr->entryFlags & ENTRY_SELECTED)) {
	    Tk_RedrawImage(mePtr->selectImage, 0, 0, imageWidth, imageHeight,
		    d, leftEdge + imageXOffset,
		    y + (mePtr->height - imageHeight)/2 + imageYOffset);
	} else {
	    Tk_RedrawImage(mePtr->image, 0, 0, imageWidth, imageHeight,
		    d, leftEdge + imageXOffset,
		    y + (mePtr->height - imageHeight)/2 + imageYOffset);
	}
    } else if (mePtr->bitmapPtr != NULL) {
	XCopyPlane(menuPtr->display, bitmap, d, gc, 0, 0, imageWidth,
		imageHeight, leftEdge + imageXOffset,
		y + (mePtr->height - imageHeight)/2  + imageYOffset, 1);
    }
    if (haveText) {
	int baseline = y + (height + fmPtr->ascent - fmPtr->descent)/2;

	Tk_DrawChars(menuPtr->display, d, gc, tkfont,
		Tcl_DStringValue(&itemTextDString),
		Tcl_DStringLength(&itemTextDString),
		leftEdge + menuTextLeadingEdgeMargin + textXOffset,
		baseline + textYOffset);
	Tcl_DStringFree(&itemTextDString);
    }

    if (mePtr->state == ENTRY_DISABLED) {
	if (menuPtr->disabledFgPtr == NULL) {
	    /* XFillRectangle(menuPtr->display, d, menuPtr->disabledGC, x, y,
		    width, height); */
	} else if ((mePtr->image != NULL)
		&& (menuPtr->disabledImageGC != None)) {
	    XFillRectangle(menuPtr->display, d, menuPtr->disabledImageGC,
		    leftEdge + imageXOffset,
		    y + (mePtr->height - imageHeight)/2 + imageYOffset,
		    imageWidth, imageHeight);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DrawMenuEntryBackground --
 *
 *	This procedure draws the background part of a menu entry.
 *	Under Appearance, we only draw the background if the entry's
 *	border is set, we DO NOT inherit it from the menu...
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands are output to X to display the menu in its
 *	current mode.
 *
 *----------------------------------------------------------------------
 */

void
DrawMenuEntryBackground(
    TkMenu *menuPtr,		/* The menu we are drawing. */
    TkMenuEntry *mePtr,		/* The entry we are drawing. */
    Drawable d,			/* What we are drawing into */
    Tk_3DBorder activeBorder,	/* Border for active items */
    Tk_3DBorder bgBorder,	/* Border for the background */
    int x,			/* left edge */
    int y,			/* top edge */
    int width,			/* width of rectangle to draw */
    int height)			/* height of rectangle to draw */
{
    if ((menuPtr->menuType == TEAROFF_MENU)
	    || ((mePtr->state == ENTRY_ACTIVE)
		    && (mePtr->activeBorderPtr != None))
	    || ((mePtr->state != ENTRY_ACTIVE) && (mePtr->borderPtr != None))) {
	if (mePtr->state == ENTRY_ACTIVE) {
	    bgBorder = activeBorder;
	}
	Tk_Fill3DRectangle(menuPtr->tkwin, d, bgBorder,
		x, y, width, height, 0, TK_RELIEF_FLAT);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetMenuLabelGeometry --
 *
 *	Figures out the size of the label portion of a menu item.
 *
 * Results:
 *	widthPtr and heightPtr are filled in with the correct geometry
 *	information.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
GetMenuLabelGeometry(
    TkMenuEntry *mePtr,		/* The entry we are computing */
    Tk_Font tkfont,		/* The precalculated font */
    const Tk_FontMetrics *fmPtr,/* The precalculated metrics */
    int *widthPtr,		/* The resulting width of the label portion */
    int *heightPtr)		/* The resulting height of the label portion */
{
    TkMenu *menuPtr = mePtr->menuPtr;
    int haveImage = 0, tornOff = (menuPtr->menuType == TEAROFF_MENU);
#ifdef USE_TK_MDEF
    const int useMDEF = ((MacMenu *) menuPtr->platformData)->useMDEF;
#endif

    if (mePtr->image != NULL && (useMDEF || tornOff)) {
	Tk_SizeOfImage(mePtr->image, widthPtr, heightPtr);
	haveImage = 1;
    } else if (mePtr->bitmapPtr != NULL && (useMDEF || tornOff)) {
	Pixmap bitmap = Tk_GetBitmapFromObj(menuPtr->tkwin, mePtr->bitmapPtr);
	Tk_SizeOfBitmap(menuPtr->display, bitmap, widthPtr, heightPtr);
	haveImage = 1;
    }
    if (!haveImage || (mePtr->compound != COMPOUND_NONE)) {
	int textWidth = 0, textHeight = fmPtr->linespace;

	if (mePtr->labelPtr != NULL) {
	    Tcl_DString itemTextDString;

	    GetEntryText(mePtr, &itemTextDString);
	    textWidth = Tk_TextWidth(tkfont,
		    Tcl_DStringValue(&itemTextDString),
		    Tcl_DStringLength(&itemTextDString)) +
		    menuTextLeadingEdgeMargin + menuTextTrailingEdgeMargin;
	    Tcl_DStringFree(&itemTextDString);

	    if (haveImage && (mePtr->compound != COMPOUND_NONE)) {
		switch ((enum compound) mePtr->compound) {
		    int margin;

		    case COMPOUND_TOP:
		    case COMPOUND_BOTTOM:
			if (textWidth > *widthPtr) {
			    *widthPtr = textWidth;
			}
			*heightPtr += textHeight + 2;
			break;
		    case COMPOUND_LEFT:
			margin = *widthPtr + 2;
			if (margin > menuTextLeadingEdgeMargin) {
			    margin = menuTextLeadingEdgeMargin;
			}
			*widthPtr += textWidth + 2 - margin;
			if (textHeight > *heightPtr) {
			    *heightPtr = textHeight;
			}
			break;
		    case COMPOUND_RIGHT:
			margin = menuTextTrailingEdgeMargin;
			*widthPtr += textWidth + 2 - margin;
			if (textHeight > *heightPtr) {
			    *heightPtr = textHeight;
			}
			break;
		    case COMPOUND_CENTER:
			if (textWidth > *widthPtr) {
			    *widthPtr = textWidth;
			}
			if (textHeight > *heightPtr) {
			    *heightPtr = textHeight;
			}
			break;
		    case COMPOUND_NONE:
			/*
			 * Never reached.
			 */
			break;
		}
		goto labelGeomDone;
	    }
	}
	*widthPtr = textWidth;
	*heightPtr = textHeight;
    }

labelGeomDone:
    *heightPtr += menuItemExtraHeight;
    *widthPtr += menuItemExtraWidth;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXGenerateParentMenuSelectEvent --
 *
 *	Respond to a hierarchical menu being opened.
 *
 * Results:
 *	True if event(s) are generated - false otherwise.
 *
 * Side effects:
 *	Places a virtual event on the event queue.
 *
 *----------------------------------------------------------------------
 */

int
TkMacOSXGenerateParentMenuSelectEvent(
    MenuRef menu)
{
    TkMenu *menuPtr = MenuPtrForMenuRef(menu);

    if (menuPtr) {
	TkMenuEntry *parentEntryPtr = GetParentMenuEntry(menuPtr);

	if (parentEntryPtr && (menuPtr = parentEntryPtr->menuPtr)) {
	    TkActivateMenuEntry(menuPtr, parentEntryPtr->index);
	    MenuSelectEvent(menuPtr);
	    Tcl_ServiceAll();
	    return true;
	}
    }
    return false;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXGenerateMenuSelectEvent --
 *
 *	Respond to a menu item being selected.
 *
 * Results:
 *	True if event(s) are generated - false otherwise.
 *
 * Side effects:
 *	Places a virtual event on the event queue.
 *
 *----------------------------------------------------------------------
 */

int
TkMacOSXGenerateMenuSelectEvent(
    MenuRef menu,
    MenuItemIndex index)
{
    TkMenu *menuPtr = MenuPtrForMenuRef(menu);
    int item = index - 1;

    if (menuPtr) {
	if (item < 0 || item >= menuPtr->numEntries ||
		(menuPtr->entries[item])->state == ENTRY_DISABLED) {
	    TkActivateMenuEntry(menuPtr, -1);
	} else {
	    TkActivateMenuEntry(menuPtr, item);
	    MenuSelectEvent(menuPtr);
	    Tcl_ServiceAll();
	    return true;
	}
    }
    return false;
}

/*
 *----------------------------------------------------------------------
 *
 * MenuSelectEvent --
 *
 *	Generates a "MenuSelect" virtual event. This can be used to
 *	do context-sensitive menu help.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Places a virtual event on the event queue.
 *
 *----------------------------------------------------------------------
 */

void
MenuSelectEvent(
    TkMenu *menuPtr)		/* the menu we have selected. */
{
    XVirtualEvent event;

    bzero(&event, sizeof(XVirtualEvent));
    event.type = VirtualEvent;
    event.serial = menuPtr->display->request;
    event.send_event = false;
    event.display = menuPtr->display;
    Tk_MakeWindowExist(menuPtr->tkwin);
    event.event = Tk_WindowId(menuPtr->tkwin);
    event.root = XRootWindow(menuPtr->display, 0);
    event.subwindow = None;
    event.time = TkpGetMS();

    XQueryPointer(NULL, None, NULL, NULL, &event.x_root, &event.y_root, NULL,
	    NULL, &event.state);
    event.same_screen = true;
    event.name = Tk_GetUid("MenuSelect");
    Tk_QueueWindowEvent((XEvent *) &event, TCL_QUEUE_TAIL);
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXClearActiveMenu --
 *
 *	Clears Tk's active entry for the given MenuRef.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Generates <<MenuSelect>> virtual events.
 *
 *----------------------------------------------------------------------
 */

void
TkMacOSXClearActiveMenu(
    MenuRef menu)
{
    TkMenu *menuPtr = MenuPtrForMenuRef(menu);

    if (menuPtr) {
	RecursivelyClearActiveMenu(menuPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RecursivelyClearActiveMenu --
 *
 *	Recursively clears the active entry in the menu's cascade hierarchy.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Generates <<MenuSelect>> virtual events.
 *
 *----------------------------------------------------------------------
 */

void
RecursivelyClearActiveMenu(
    TkMenu *menuPtr)		/* The menu to reset. */
{
    int i;
    TkMenuEntry *mePtr;

    TkActivateMenuEntry(menuPtr, -1);
    for (i = 0; i < menuPtr->numEntries; i++) {
	mePtr = menuPtr->entries[i];
	if (mePtr->type == CASCADE_ENTRY) {
	    if ((mePtr->childMenuRefPtr != NULL)
		    && (mePtr->childMenuRefPtr->menuPtr != NULL)) {
		RecursivelyClearActiveMenu(mePtr->childMenuRefPtr->menuPtr);
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXClearMenubarActive --
 *
 *	Recursively clears the active entry in the current menubar hierarchy.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Generates <<MenuSelect>> virtual events.
 *
 *----------------------------------------------------------------------
 */

void
TkMacOSXClearMenubarActive(void)
{
    TkMenuReferences *menuBarRefPtr;

    if (currentMenuBarName != NULL) {
	menuBarRefPtr = TkFindMenuReferences(currentMenuBarInterp,
		currentMenuBarName);
	if ((menuBarRefPtr != NULL) && (menuBarRefPtr->menuPtr != NULL)) {
	    TkMenu *menuPtr;

	    for (menuPtr = menuBarRefPtr->menuPtr->masterMenuPtr;
		    menuPtr != NULL; menuPtr = menuPtr->nextInstancePtr) {
		if (menuPtr->menuType == MENUBAR) {
		    RecursivelyClearActiveMenu(menuPtr);
		}
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkpMenuNotifyToplevelCreate --
 *
 *	This routine reconfigures the menu and the clones indicated by
 *	menuName becuase a toplevel has been created and any system
 *	menus need to be created. Only applicable to Windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	An idle handler is set up to do the reconfiguration.
 *
 *----------------------------------------------------------------------
 */

void
TkpMenuNotifyToplevelCreate(
    Tcl_Interp *interp,		/* The interp the menu lives in. */
    char *menuName)		/* The name of the menu to reconfigure. */
{
    /*
     * Nothing to do.
     */
}

/*
 *----------------------------------------------------------------------
 *
 * TkpMenuInit --
 *
 *	Initializes Mac-specific menu data.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Allocates a hash table.
 *
 *----------------------------------------------------------------------
 */

void
TkpMenuInit(void)
{
    MenuSymbol *ms = menuSymbols;
    CFStringRef cfStr;

    lastMenuID = 256;
    Tcl_InitHashTable(&commandTable, TCL_ONE_WORD_KEYS);
    currentMenuBarOwner = NULL;
    currentAppleMenuID = 0;
    currentHelpMenuID = 0;
    currentMenuBarInterp = NULL;
    currentMenuBarName = NULL;
    windowListPtr = NULL;

#ifdef USE_TK_MDEF
    tkThemeMenuItemDrawingUPP
	    = NewMenuItemDrawingUPP(ThemeMenuItemDrawingProc);
    useMDEFVar = Tcl_NewStringObj("::tk::mac::useCustomMDEF", -1);
    macMDEFDrawable.winPtr = NULL;
    macMDEFDrawable.xOff = 0;
    macMDEFDrawable.yOff = 0;
    macMDEFDrawable.visRgn = NULL;
    macMDEFDrawable.aboveVisRgn = NULL;
    macMDEFDrawable.drawRect = CGRectNull;
    macMDEFDrawable.referenceCount = 0;
    macMDEFDrawable.toplevel = NULL;
    macMDEFDrawable.flags = 0;
    macMDEFDrawable.grafPtr = NULL;
    macMDEFDrawable.context = NULL;
    macMDEFDrawable.size = CGSizeZero;
#endif

    ChkErr(GetThemeMetric, kThemeMetricMenuMarkColumnWidth,
	    &menuMarkColumnWidth);
    ChkErr(GetThemeMetric, kThemeMetricMenuMarkIndent, &menuMarkIndent);
    ChkErr(GetThemeMetric, kThemeMetricMenuTextLeadingEdgeMargin,
	    &menuTextLeadingEdgeMargin);
    ChkErr(GetThemeMetric, kThemeMetricMenuTextTrailingEdgeMargin,
	    &menuTextTrailingEdgeMargin);
    ChkErr(GetThemeMenuItemExtra, kThemeMenuItemPlain, &menuItemExtraHeight,
	    &menuItemExtraWidth);
    ChkErr(GetThemeMenuSeparatorHeight, &menuSeparatorHeight);

    while (ms->unicode) {
	ms->utfLen = Tcl_UniCharToUtf(ms->unicode, ms->utf);
	ms->utf[ms->utfLen] = 0;
	cfStr = CFStringCreateWithCharacters(NULL, &ms->unicode, 1);
	if (cfStr) {
	    ms->width = MeasureThemeText(cfStr, kThemeMenuItemCmdKeyFont);
	    CFRelease(cfStr);
	}
	ms++;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkpMenuThreadInit --
 *
 *	Does platform-specific initialization of thread-specific
 *	menu state.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TkpMenuThreadInit(void)
{
    /*
     * Nothing to do.
     */
}

/*
 *----------------------------------------------------------------------
 *
 * TkpPreprocessMacMenu --
 *
 *    Handle preprocessing of menubar if it exists.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    All post commands for the current menubar get executed.
 *
 *----------------------------------------------------------------------
 */

void
TkMacOSXPreprocessMenu(void)
{
    if ((currentMenuBarName != NULL) && (currentMenuBarInterp != NULL)) {
	TkMenuReferences *mbRefPtr =
		TkFindMenuReferences(currentMenuBarInterp,currentMenuBarName);

	if ((mbRefPtr != NULL) && (mbRefPtr->menuPtr != NULL)) {
	    int code;

	    Tcl_Preserve((ClientData) currentMenuBarInterp);
	    code = TkPreprocessMenu(mbRefPtr->menuPtr->masterMenuPtr);
	    if ((code != TCL_OK) && (code != TCL_CONTINUE)
		    && (code != TCL_BREAK)) {
		Tcl_AddErrorInfo(currentMenuBarInterp,
			"\n    (menu preprocess)");
		Tcl_BackgroundError(currentMenuBarInterp);
	    }
	    Tcl_Release((ClientData) currentMenuBarInterp);
	}
    }
}

#ifdef USE_TK_MDEF
#pragma mark MDEF
/*
 *----------------------------------------------------------------------
 *
 * MenuDefProc --
 *
 *	This routine is the MDEF handler for Tk. It receives all messages
 *	for the menu and dispatches them.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	This routine causes menus to be drawn and will certainly allocate
 *	memory as a result. Also, the menu can scroll up and down, and
 *	various other interface actions can take place.
 *
 *----------------------------------------------------------------------
 */

void
MenuDefProc(
    SInt16 message,		/* What action are we taking? */
    MenuRef menu,		/* The menu we are working with */
    Rect *menuRectPtr,		/* A pointer to the rect for the
				 * whole menu. */
    Point hitPt,		/* Where the mouse was clicked for
				 * the appropriate messages. */
    SInt16 *whichItem)		/* Output result. Which item was
				 * hit by the user? */
{
    TkMenu *menuPtr;
    Tcl_HashEntry *commandEntryPtr;
    MenuID menuID;

    menuID = GetMenuID(menu);
    commandEntryPtr = Tcl_FindHashEntry(&commandTable, (char*)(intptr_t)menuID);

    if (!commandEntryPtr) return;
    menuPtr = (TkMenu *) Tcl_GetHashValue(commandEntryPtr);

    switch (message) {
	case kMenuInitMsg:
	    *whichItem = noErr;
	    break;
	case kMenuDisposeMsg:
	    break;
	case kMenuHiliteItemMsg:
	    HandleMenuHiliteMsg(menu, menuRectPtr, hitPt, whichItem, menuPtr);
	    break;
	case kMenuCalcItemMsg:
	    HandleMenuCalcItemMsg(menu, menuRectPtr, hitPt, whichItem,
		    menuPtr);
	    break;
	case kMenuDrawItemsMsg:
#ifdef TK_MAC_DEBUG_MENUS
	    TkMacOSXDbgMsg("MDEF: DrawItemsMsg");
#endif
	    /*
	     * We do nothing  here, because we don't support the Menu Managers
	     * dynamic item groups
	     */
	    break;
	case kMenuThemeSavvyMsg:
	    *whichItem = kThemeSavvyMenuResponse;
	    break;
	case kMenuSizeMsg:
#ifdef TK_MAC_DEBUG_MENUS
	    TkMacOSXDbgMsg("MDEF: SizeMsg %d, %d", hitPt.h, hitPt.v);
#endif
	    SetMenuWidth(menu, hitPt.h < menuPtr->totalWidth ?	hitPt.h :
		    menuPtr->totalWidth);
	    SetMenuHeight(menu, hitPt.v < menuPtr->totalHeight ? hitPt.v :
		    menuPtr->totalHeight);
	    break;
	case kMenuDrawMsg:
	    HandleMenuDrawMsg(menu, menuRectPtr, hitPt, whichItem, menuPtr);
	    break;
	case kMenuFindItemMsg:
	    HandleMenuFindItemMsg(menu, menuRectPtr, hitPt, whichItem,
		    menuPtr);
	    break;
	case kMenuPopUpMsg:
	    HandleMenuPopUpMsg(menu, menuRectPtr, hitPt, whichItem, menuPtr);
	    break;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * HandleMenuHiliteMsg --
 *
 *	Handles the MenuDefProc's hilite message.
 *
 * Results:
 *	A menu entry is drawn
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

void
HandleMenuHiliteMsg(
    MenuRef menu,
    Rect *menuRectPtr,
    Point hitPt,
    SInt16 *whichItem,
    TkMenu *menuPtr)
{
    OSStatus err;
    Tk_Font tkfont;
    Tk_FontMetrics fontMetrics;
    MDEFHiliteItemData *hidPtr = (MDEFHiliteItemData *)whichItem;
    int oldItem = hidPtr->previousItem - 1;
    int newItem = hidPtr->newItem - 1;
    MenuTrackingData mtd, *mtdPtr = &mtd;

#ifdef TK_MAC_DEBUG_MENUS
    TkMacOSXDbgMsg("MDEF: HiliteMsg %d -> %d", hidPtr->previousItem,
	    hidPtr->newItem);
#endif
    GetPort(&macMDEFDrawable.grafPtr);
    macMDEFDrawable.context = (CGContextRef) hidPtr->context;

    err = ChkErr(GetMenuTrackingData, menu, mtdPtr);
    if (err != noErr) {
	return;
    }

    tkfont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr);
    Tk_GetFontMetrics(tkfont, &fontMetrics);
    if (oldItem >= 0) {
	AppearanceEntryDrawWrapper(menuPtr->entries[oldItem], menuRectPtr,
		mtdPtr, (Drawable) &macMDEFDrawable, &fontMetrics, tkfont, 1);
    }
    if (newItem >= 0) {
	AppearanceEntryDrawWrapper(menuPtr->entries[newItem], menuRectPtr,
		mtdPtr, (Drawable) &macMDEFDrawable, &fontMetrics, tkfont, 0);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * HandleMenuDrawMsg --
 *
 *	Handles the MenuDefProc's draw message.
 *
 * Results:
 *	A menu entry is drawn
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

void
HandleMenuDrawMsg(
    MenuRef menu,
    Rect *menuRectPtr,
    Point hitPt,
    SInt16 *whichItem,
    TkMenu *menuPtr)
{
    Tk_Font menuFont;
    Tk_FontMetrics fontMetrics;
    TkMenuEntry *mePtr;
    int i;
    Rect menuClipRect, bounds;
    MDEFDrawData *ddPtr = (MDEFDrawData*)whichItem;
    MenuTrackingData *mtdPtr = &(ddPtr->trackingData);
    TkWindow *winPtr = (TkWindow*)menuPtr->tkwin;

    GetPort(&macMDEFDrawable.grafPtr);
    GetPortBounds(macMDEFDrawable.grafPtr, &bounds);
    macMDEFDrawable.context = (CGContextRef) ddPtr->context;
#ifdef TK_MAC_DEBUG_MENUS
    TkMacOSXDbgMsg("MDEF: DrawMsg %d - %d; %d - %d", menuRectPtr->top,
	    menuRectPtr->bottom, bounds.top, bounds.bottom);
#endif
    winPtr->changes.x = menuRectPtr->left;
    winPtr->changes.y = menuRectPtr->top;
    winPtr->changes.width = menuRectPtr->right - menuRectPtr->left;
    winPtr->changes.height = menuRectPtr->bottom - menuRectPtr->top;
    TkpClipDrawableToRect(menuPtr->display, (Drawable) &macMDEFDrawable,
	    0, 0, -1, -1);
#if 0
    if (menuPtr->menuRefPtr->topLevelListPtr != NULL) {
	menuType = kThemeMenuTypePullDown;
    } else if (menuPtr->menuRefPtr->parentEntryPtr != NULL) {
	menuType = kThemeMenuTypeHierarchical;
    } else {
	menuType = kThemeMenuTypePopUp;
    }
#endif
    DrawMenuBackground(menuPtr, menuRectPtr, (Drawable) &macMDEFDrawable);
    menuFont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr);
    Tk_GetFontMetrics(menuFont, &fontMetrics);
    menuClipRect = *menuRectPtr;
    mtdPtr->virtualMenuBottom = mtdPtr->virtualMenuTop + menuPtr->totalHeight;

    /*
     * Next, figure out scrolling information.
     */

    if ((menuRectPtr->bottom - menuRectPtr->top) < menuPtr->totalHeight) {
	short arrowHeight = fontMetrics.linespace + 1;
	Rect arrowRect, eraseRect;
	ThemeMenuState menuState = IsMenuItemEnabled(menu, 0) ?
		kThemeMenuActive : kThemeMenuDisabled;

	if (mtdPtr->virtualMenuTop < menuRectPtr->top) {
	    arrowRect = bounds;
	    /*arrowRect.top += 1;*/
	    arrowRect.bottom = arrowRect.top + arrowHeight;
	    eraseRect = arrowRect;
	    eraseRect.top = menuRectPtr->top;
	    menuClipRect.top = arrowRect.bottom;
	    ChkErr(EraseMenuBackground, menu, &eraseRect,
		    macMDEFDrawable.context);
	    ChkErr(DrawThemeMenuItem, menuRectPtr, &arrowRect,
		    mtdPtr->virtualMenuTop, mtdPtr->virtualMenuBottom,
		    menuState, kThemeMenuItemScrollUpArrow, NULL, 0);
#ifdef TK_MAC_DEBUG_MENUS
	    TkMacOSXDbgMsg("upArrow:   %d - %d, %d - %d", arrowRect.top,
		    arrowRect.bottom, arrowRect.left, arrowRect.right);
#endif
	}
	if (mtdPtr->virtualMenuBottom > menuRectPtr->bottom) {
	    arrowRect = bounds;
	    arrowRect.bottom -= 1;
	    arrowRect.top = arrowRect.bottom - arrowHeight;
	    eraseRect = arrowRect;
	    eraseRect.bottom = menuRectPtr->bottom;
	    menuClipRect.bottom = arrowRect.top;
	    ChkErr(EraseMenuBackground, menu, &eraseRect,
		    macMDEFDrawable.context);
	    ChkErr(DrawThemeMenuItem, menuRectPtr, &arrowRect,
		    mtdPtr->virtualMenuTop, mtdPtr->virtualMenuBottom,
		    menuState, kThemeMenuItemScrollDownArrow, NULL, 0);
#ifdef TK_MAC_DEBUG_MENUS
	    TkMacOSXDbgMsg("downArrow: %d - %d, %d - %d", arrowRect.top,
		    arrowRect.bottom, arrowRect.left, arrowRect.right);
#endif
	}
	TkpClipDrawableToRect(menuPtr->display, (Drawable) &macMDEFDrawable,
		menuClipRect.left, menuClipRect.top, menuClipRect.right -
		menuClipRect.left, menuClipRect.bottom - menuClipRect.top);
    }

    /*
     * Now, actually draw the menu. Don't draw entries that
     * are higher than the top arrow, and don't draw entries
     * that are lower than the bottom.
     */

    for (i = 0; i < menuPtr->numEntries; i++) {
	mePtr = menuPtr->entries[i];
	if (mtdPtr->virtualMenuTop + mePtr->y + mePtr->height <
		menuClipRect.top || mtdPtr->virtualMenuTop + mePtr->y >
		menuClipRect.bottom) {
	    continue;
	}
	AppearanceEntryDrawWrapper(mePtr, menuRectPtr, mtdPtr,
		(Drawable) &macMDEFDrawable, &fontMetrics, menuFont, 0);
    }
    MDEFScrollFlag = 1;
}

/*
 *----------------------------------------------------------------------
 *
 * HandleMenuFindItemMsg --
 *
 *	Handles the MenuDefProc's FindItems message. We have to
 *	respond by filling in the itemSelected, itemUnderMouse and
 *	itemRect fields. This is also the time to scroll the menu if
 *	it is too long to fit on the screen.
 *
 * Results:
 *	The Menu system is informed of the selected item & the item
 *	under the mouse.
 *
 * Side effects:
 *	The menu might get scrolled.
 *
 *----------------------------------------------------------------------
 */
void
HandleMenuFindItemMsg(
    MenuRef menu,
    Rect *menuRectPtr,
    Point hitPt,
    SInt16 *whichItem,
    TkMenu *menuPtr)
{
    Tk_Font menuFont;
    Tk_FontMetrics fontMetrics;
    TkMenuEntry *mePtr;
    int i, newItem = -1, itemUnderMouse = -1;
    Rect itemRect = {0, 0, 0, 0}, menuClipRect, bounds;
    int hasTopScroll, hasBottomScroll;
    MDEFFindItemData *fiPtr = (MDEFFindItemData *)whichItem;
    MenuTrackingData *mtdPtr = &(fiPtr->trackingData), topMtd;
    enum {
	DONT_SCROLL, DOWN_SCROLL, UP_SCROLL
    } scrollDirection;
    short arrowHeight;

#ifdef TK_MAC_DEBUG_MENUS
    static Point lastHitPt = {0, 0};
    if (hitPt.h != lastHitPt.h || hitPt.v != lastHitPt.v) {
	lastHitPt = hitPt;
	TkMacOSXDbgMsg("MDEF: FindItemMsg: %d, %d", hitPt.h, hitPt.v);
    }
#endif

    GetPort(&macMDEFDrawable.grafPtr);
    GetPortBounds(macMDEFDrawable.grafPtr, &bounds);
    macMDEFDrawable.context = (CGContextRef) fiPtr->context;

    /*
     * Now we need to take care of scrolling the menu.
     */

    menuFont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr);
    Tk_GetFontMetrics(menuFont, &fontMetrics);
    arrowHeight = fontMetrics.linespace + 1;
    menuClipRect = *menuRectPtr;
    hasTopScroll = mtdPtr->virtualMenuTop < menuRectPtr->top;
    hasBottomScroll = mtdPtr->virtualMenuBottom > menuRectPtr->bottom;
    scrollDirection = DONT_SCROLL;
    if (hasTopScroll) {
	menuClipRect.top = bounds.top + arrowHeight;
	if (hitPt.v < menuClipRect.top) {
	    newItem = -1;
	    scrollDirection = DOWN_SCROLL;
	}
    }
    if (hasBottomScroll) {
	menuClipRect.bottom = bounds.bottom - 1 - arrowHeight;
	if (hitPt.v > menuClipRect.bottom) {
	    newItem = -1;
	    scrollDirection = UP_SCROLL;
	}
    }
    if (MDEFScrollFlag) {
	scrollDirection = DONT_SCROLL;
	MDEFScrollFlag = 0;
    }
    /*
     * Don't scroll if there are other menus open above us
     */
    ChkErr(GetMenuTrackingData, NULL, &topMtd);
    if (menu != topMtd.menu) {
	scrollDirection = DONT_SCROLL;
    }
    if (scrollDirection == DONT_SCROLL) {
	/*
	 * Find out which item was hit. If it is the same as the old item,
	 * we don't need to do anything.
	 */

	if (PtInRect(hitPt, menuRectPtr)) {
	    for (i = 0; i < menuPtr->numEntries; i++) {
		mePtr = menuPtr->entries[i];
		itemRect.left = menuRectPtr->left + mePtr->x;
		itemRect.top = mtdPtr->virtualMenuTop + mePtr->y;
		itemRect.right = mePtr->entryFlags & ENTRY_LAST_COLUMN ?
			menuRectPtr->right : itemRect.left + mePtr->width;
		itemRect.bottom = itemRect.top + mePtr->height;
		if (PtInRect(hitPt, &itemRect)) {
		    if ((mePtr->type == SEPARATOR_ENTRY)
			    || (mePtr->state == ENTRY_DISABLED)) {
			newItem = -1;
			itemUnderMouse = i;
		    } else {
			TkMenuEntry *parentEntryPtr =
				GetParentMenuEntry(menuPtr);

			if (parentEntryPtr &&
				parentEntryPtr->state == ENTRY_DISABLED) {
			    newItem = -1;
			    itemUnderMouse = i;
			} else {
			    newItem = i;
			    itemUnderMouse = i;
			}
		    }
		    break;
		}
	    }
	}
    } else {
	short scrollAmt;
	unsigned long scrollDelay;
	Rect arrowRect, eraseRect, scrolledMenuClipRect;
	ThemeMenuState menuState = IsMenuItemEnabled(menu, 0) ?
		kThemeMenuActive : kThemeMenuDisabled;
	int oldItem = mtdPtr->itemSelected - 1;
	short d;

	TkpClipDrawableToRect(menuPtr->display, (Drawable) &macMDEFDrawable,
		0, 0, -1, -1);
	scrollAmt = fontMetrics.linespace + menuItemExtraHeight;
	if (scrollDirection == UP_SCROLL) {
	    scrollAmt = -scrollAmt;
	    d = hitPt.v - bounds.bottom;
	} else {
	    d = bounds.top - hitPt.v;
	}
	scrollDelay = (d >= scrollAmt/2) ? 1 : 10;
	menuClipRect = *menuRectPtr;
	if (mtdPtr->virtualMenuTop + scrollAmt < menuRectPtr->top) {
	    arrowRect = bounds;
	    /*arrowRect.top += 1;*/
	    arrowRect.bottom = arrowRect.top + arrowHeight;
	    eraseRect = arrowRect;
	    eraseRect.top = menuRectPtr->top;
	    menuClipRect.top = arrowRect.bottom;
	    if (!hasTopScroll) {
		ChkErr(EraseMenuBackground, menu, &eraseRect,
			macMDEFDrawable.context);
		ChkErr(DrawThemeMenuItem, menuRectPtr, &arrowRect,
			mtdPtr->virtualMenuTop + scrollAmt,
			mtdPtr->virtualMenuBottom + scrollAmt,
			menuState, kThemeMenuItemScrollUpArrow, NULL, 0);
#ifdef TK_MAC_DEBUG_MENUS
		TkMacOSXDbgMsg("upArrow:   %d - %d, %d - %d", arrowRect.top,
			arrowRect.bottom, arrowRect.left, arrowRect.right);
#endif
	    }
	}
	if (mtdPtr->virtualMenuBottom + scrollAmt > menuRectPtr->bottom) {
	    arrowRect = bounds;
	    arrowRect.bottom -= 1;
	    arrowRect.top = arrowRect.bottom - arrowHeight;
	    eraseRect = arrowRect;
	    eraseRect.bottom = menuRectPtr->bottom;
	    menuClipRect.bottom = arrowRect.top;
	    if (!hasBottomScroll) {
		ChkErr(EraseMenuBackground, menu, &eraseRect,
			macMDEFDrawable.context);
		ChkErr(DrawThemeMenuItem, menuRectPtr, &arrowRect,
			mtdPtr->virtualMenuTop + scrollAmt,
			mtdPtr->virtualMenuBottom + scrollAmt,
			menuState, kThemeMenuItemScrollDownArrow, NULL, 0);
#ifdef TK_MAC_DEBUG_MENUS
		TkMacOSXDbgMsg("downArrow: %d - %d, %d - %d", arrowRect.top,
			arrowRect.bottom, arrowRect.left, arrowRect.right);
#endif
	    }
	}
	TkpClipDrawableToRect(menuPtr->display, (Drawable) &macMDEFDrawable,
		menuClipRect.left, menuClipRect.top, menuClipRect.right -
		menuClipRect.left, menuClipRect.bottom - menuClipRect.top);
	TkActivateMenuEntry(menuPtr, -1);
	if (oldItem >= 0) {
	    AppearanceEntryDrawWrapper(menuPtr->entries[oldItem], menuRectPtr,
		    mtdPtr, (Drawable) &macMDEFDrawable, &fontMetrics,
		    menuFont, 1);
	}
	ChkErr(ScrollMenuImage, menu, &menuClipRect, 0, scrollAmt,
		macMDEFDrawable.context);
	mtdPtr->virtualMenuTop += scrollAmt;
	mtdPtr->virtualMenuBottom += scrollAmt;
	scrolledMenuClipRect = menuClipRect;
	OffsetRect(&scrolledMenuClipRect, 0, scrollAmt);
	menuClipRect = bounds;
	if (mtdPtr->virtualMenuTop < menuRectPtr->top) {
	    menuClipRect.top += arrowHeight;
	}
	if (mtdPtr->virtualMenuBottom > menuRectPtr->bottom) {
	    menuClipRect.bottom -= arrowHeight;
	}
	TkpClipDrawableToRect(menuPtr->display, (Drawable) &macMDEFDrawable,
		menuClipRect.left, menuClipRect.top, menuClipRect.right -
		menuClipRect.left, menuClipRect.bottom - menuClipRect.top);
	if (scrolledMenuClipRect.bottom < menuClipRect.bottom) {
	    menuClipRect.top = scrolledMenuClipRect.bottom;
	} else if (scrolledMenuClipRect.top < menuClipRect.top) {
	    menuClipRect.bottom = scrolledMenuClipRect.top;
	}
	for (i = 0; i < menuPtr->numEntries; i++) {
	    mePtr = menuPtr->entries[i];
	    if (mtdPtr->virtualMenuTop + mePtr->y + mePtr->height <
		    menuClipRect.top || mtdPtr->virtualMenuTop + mePtr->y >
		    menuClipRect.bottom) {
		continue;
	    }
#ifdef TK_MAC_DEBUG_MENUS
	    TkMacOSXDbgMsg("Drawing item %i", i);
#endif
	    AppearanceEntryDrawWrapper(mePtr, menuRectPtr, mtdPtr,
		    (Drawable) &macMDEFDrawable, &fontMetrics, menuFont, 1);
	}
	Delay(scrollDelay, NULL);
    }
    mtdPtr->itemSelected = newItem + 1;
    mtdPtr->itemUnderMouse = itemUnderMouse + 1;
    mtdPtr->itemRect = itemRect;
}

/*
 *----------------------------------------------------------------------
 *
 * HandleMenuPopUpMsg --
 *
 *	Handles the MenuDefProc's PopUp message. The menu is
 *	posted with the selected item at the point given in hitPt.
 *
 * Results:
 *	A menu is posted.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
void
HandleMenuPopUpMsg(
    MenuRef menu,
    Rect *menuRectPtr,
    Point hitPt,
    SInt16 *whichItem,
    TkMenu *menuPtr)
{
    int maxMenuHeight;
    int oldItem;
    Rect portRect;
    BitMap screenBits;
    static SInt16 menuBarHeight = 0;

#ifdef TK_MAC_DEBUG_MENUS
    TkMacOSXDbgMsg("MDEF: PopUpMsg");
#endif

    if (!menuBarHeight) {
	ChkErr(GetThemeMenuBarHeight, &menuBarHeight);
    }
    GetQDGlobalsScreenBits(&screenBits);

    /*
     * Note that for some oddball reason, h and v are reversed in the
     * point given to us by the MDEF.
     */

    oldItem = *whichItem;
    if (oldItem >= menuPtr->numEntries) {
	oldItem = -1;
    }
    portRect.top = 0;
    portRect.bottom = 1280;
    maxMenuHeight = screenBits.bounds.bottom - screenBits.bounds.top
	    - menuBarHeight - SCREEN_MARGIN;
    if (menuPtr->totalHeight > maxMenuHeight) {
	menuRectPtr->top = menuBarHeight;
    } else {
	int delta;

	menuRectPtr->top = hitPt.h;
	if (oldItem >= 0) {
	    menuRectPtr->top -= menuPtr->entries[oldItem]->y;
	}

	if (menuRectPtr->top < menuBarHeight) {
	    /*
	     * Displace downward if the menu would stick off the top of the
	     * screen.
	     */

	    menuRectPtr->top = menuBarHeight + SCREEN_MARGIN;
	} else {
	    /*
	     * Or upward if the menu sticks off the bottom end...
	     */

	    delta = menuRectPtr->top + menuPtr->totalHeight - maxMenuHeight;
	    if (delta > 0) {
		menuRectPtr->top -= delta;
	    }
	}
    }
    menuRectPtr->left = hitPt.v;
    menuRectPtr->right = menuRectPtr->left + menuPtr->totalWidth;
    menuRectPtr->bottom = menuRectPtr->top +
	    ((maxMenuHeight < menuPtr->totalHeight)
	    ? maxMenuHeight : menuPtr->totalHeight);
    if (menuRectPtr->top == menuBarHeight) {
	*whichItem = hitPt.h;
    } else {
	*whichItem = menuRectPtr->top;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * HandleMenuCalcItemMsg --
 *
 *	Handles the MenuDefProc's CalcItem message. It is supposed
 *	to calculate the Rect of the menu entry in whichItem in the
 *	menu, and put that in menuRectPtr. I assume this works, but I
 *	have never seen the MenuManager send this message.
 *
 * Results:
 *	The Menu Manager is informed of the bounding rect of a
 *	menu rect.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
HandleMenuCalcItemMsg(
    MenuRef menu,
    Rect *menuRectPtr,
    Point hitPt,
    SInt16 *whichItem,
    TkMenu *menuPtr)
{
    TkMenuEntry *mePtr;
    MenuTrackingData mtd, *mtdPtr = &mtd;
    OSStatus err;
    int virtualTop, item = *whichItem-1;

    err = ChkErr(GetMenuTrackingData, menu, mtdPtr);
    if (err == noErr) {
	virtualTop = mtdPtr->virtualMenuTop;
    } else {
	virtualTop = 0;
    }

    if (item >= 0 && item < menuPtr->numEntries) {
	mePtr = menuPtr->entries[item];
	menuRectPtr->left = mePtr->x;
	menuRectPtr->top = mePtr->y + virtualTop;
	if (mePtr->entryFlags & ENTRY_LAST_COLUMN) {
	    menuRectPtr->right = menuPtr->totalWidth;
	} else {
	    menuRectPtr->right = mePtr->x + mePtr->width;
	}
	menuRectPtr->bottom = menuRectPtr->top + mePtr->height;
    }
#ifdef TK_MAC_DEBUG_MENUS
    TkMacOSXDbgMsg("MDEF: CalcItemMsg %d: %d, %d", *whichItem,
	    menuRectPtr->left, menuRectPtr->top);
#endif
}
#endif /* USE_TK_MDEF */
