/*
 *	$Source: /u1/X/xterm/RCS/menu.c,v $
 *	$Header: menu.c,v 10.101 86/12/01 17:52:43 swick Rel $
 */

#ifdef MODEMENU
#include "X/Xlib.h"
#include "menu.h"

#ifndef lint
static char sccs_id[] = "@(#)menu.c\tX10/6.6\t11/3/86";
#endif	lint

#define	FALSE			0
#define	TRUE			1
#define	InvertPlane		1
#define	SetStateFlags(item)	item->itemFlags = (item->itemFlags &\
				 ~(itemStateMask | itemChanged)) |\
				 ((item->itemFlags & itemSetMask) >>\
				 itemSetMaskShift)


static short Check_MarkBits[] = {
   0x0100, 0x0180, 0x00c0, 0x0060,
   0x0031, 0x001b, 0x000e, 0x0004
};
static short Check_GrayBits[] = {
   0x0100, 0x0080, 0x0040, 0x0020,
   0x0011, 0x000a, 0x0004, 0x0000
};
static short Default_CursorBits[] = {
   0x0000, 0x0002, 0x0006, 0x000e,
   0x001e, 0x003e, 0x007e, 0x00fe,
   0x01fe, 0x003e, 0x0036, 0x0062,
   0x0060, 0x00c0, 0x00c0, 0x0000
};
static short Default_GrayBits[] = {
   0xaaaa, 0x5555, 0xaaaa, 0x5555,
   0xaaaa, 0x5555, 0xaaaa, 0x5555,
   0xaaaa, 0x5555, 0xaaaa, 0x5555,
   0xaaaa, 0x5555, 0xaaaa, 0x5555,
};
static short Default_MaskBits[] = {
   0x0003, 0x0007, 0x000f, 0x001f,
   0x003f, 0x007f, 0x00ff, 0x01ff,
   0x03ff, 0x07ff, 0x007f, 0x00f7,
   0x00f3, 0x01e1, 0x01e0, 0x01c0
};
static char def_menu_font[] = "vtsingle";

Pixmap Gray_Tile;
Menu Menu_Default;
Cursor Menu_DefaultCursor;
char *Menu_DefaultFont;
FontInfo *Menu_DefaultFontInfo;

/*
 * AddMenuItem() adds a menu item to an existing menu, at the end of the
 * list, which are number sequentially from zero.  The menuitem index is
 * return, or -1 if failed.
 */

AddMenuItem(menu, text)
register Menu *menu;
register char *text;
{
	register MenuItem *menuitem, **next;
	register int i;
	extern char *malloc();

	if(!menu || !text || (menuitem = (MenuItem *)malloc(sizeof(MenuItem)))
	 == (MenuItem *)0)
		return(-1);
	bzero((char *)menuitem, sizeof(MenuItem));
	menuitem->itemText = text;
	menuitem->itemTextLength = strlen(text);
	for(i = 0, next = &menu->menuItems ; *next ; i++)
		next = &(*next)->nextItem;
	*next = menuitem;
	menu->menuFlags |= menuChanged;
	return(i);
}

/*
 * DisposeItem() releases the memory allocated for the given indexed
 * menuitem.  Nonzero is returned if an item was actual disposed of.
 */
DisposeItem(menu, i)
register Menu *menu;
register int i;
{
	register MenuItem **next, **last, *menuitem;

	if(!menu || i < 0)
		return(0);
	next = &menu->menuItems;
	do {
		if(!*next)
			return(0);
		last = next;
		next = &(*next)->nextItem;
	} while(i-- > 0);
	menuitem = *last;
	*last = *next;
	free(menuitem);
	return(1);
}

/*
 * DisposeMenu() releases the memory allocated for the given menu.
 */
DisposeMenu(menu)
register Menu *menu;
{
	static Unmap_Menu();

	if(!menu)
		return;
	if(menu->menuFlags & menuMapped)
		Unmap_Menu(menu);
	while(DisposeItem(menu, 0));
	if(menu->menuWindow)
		XDestroyWindow(menu->menuWindow);
	if(menu->menuSaved)
		XFreePixmap(menu->menuSaved);
	free(menu);
}

InitMenu(name)
register char *name;
{
	register char *cp;
	register Bitmap bit;

	/*
	 * If the gray tile hasn't been set up, do it now.
	 */
	if(!Gray_Tile) {
		if(!(bit = XStoreBitmap(grayWidth, grayHeight,
		 Default_GrayBits)))
			return;
		Gray_Tile = XMakePixmap(bit, WhitePixel, BlackPixel);
		XFreeBitmap(bit);
	}
	Menu_Default.menuFlags = menuChanged;
	if((cp = XGetDefault(name, "MenuFreeze")) && strcmp(cp, "on") == 0)
		Menu_Default.menuFlags |= menuFreeze;
	if((cp = XGetDefault(name, "MenuSave")) && strcmp(cp, "on") == 0)
		Menu_Default.menuFlags |= menuSaveMenu;
	Menu_Default.menuInitialItem = -1;
	Menu_Default.menuBorderWidth = (cp = XGetDefault(name, "MenuBorder")) ?
	 atoi(cp) : 2;
	Menu_Default.menuItemPad = (cp = XGetDefault(name, "MenuPad")) ?
	 atoi(cp) : 3;
	Menu_DefaultFont = (cp = XGetDefault(name, "MenuFont")) ? cp :
	 def_menu_font;
};

/*
 * ItemFlags returns the state of item "n" of the menu.
 */
ItemFlags(menu, n)
register Menu *menu;
register int n;
{
	register MenuItem *item;

	if(!menu || !menu->menuItems || n < 0)
		return(-1);
	for(item = menu->menuItems ; n > 0 ; n--)
		if(!(item = item->nextItem))
			return(0);
	return((item->itemFlags & itemSetMask) >> itemSetMaskShift);
}

/*
 * ItemText changes the text of item "n" of the menu.
 */
ItemText(menu, n, text)
register Menu *menu;
register int n;
char *text;
{
	register MenuItem *item;

	if(!menu || !menu->menuItems || n < 0 || !text)
		return(0);
	for(item = menu->menuItems ; n > 0 ; n--)
		if(!(item = item->nextItem))
			return(0);
	item->itemText = text;
	menu->menuFlags |= menuChanged;
	return(1);
}

/*
 * NewMenu() returns a pointer to an initialized new Menu structure, or NULL
 * if failed.
 *
 * The Menu structure _menuDefault contains the default menu settings.
 */
Menu *NewMenu(name, reverse)
char *name;
int reverse;
{
	register Menu *menu;
	register int fg, bg;
	extern char *malloc();

	/*
	 * If the GrayTile hasn't been defined, InitMenu() was never
	 * run, so exit.
	 */
	if(!Gray_Tile)
		return((Menu *)0);
	/*
	 * Allocate the memory for the menu structure.
	 */
	if((menu = (Menu *)malloc(sizeof(Menu))) == (Menu *)0)
		return((Menu *)0);
	/*
	 * Initialize to default values.
	 */
	*menu = Menu_Default;
	/*
	 * If the menu font hasn't yet been gotten, go get it.
	 */
	if(!menu->menuFontInfo) {
		if(!Menu_DefaultFontInfo && !(Menu_DefaultFontInfo =
		 XOpenFont(Menu_DefaultFont)))
			return((Menu *)0);
		menu->menuFontInfo = Menu_DefaultFontInfo;
	}
	/*
	 * If the menu cursor hasn't been given, make a default one.
	 */
	if(!menu->menuCursor) {
		if(!Menu_DefaultCursor) {
			if(reverse) {
				fg = WhitePixel;
				bg = BlackPixel;
			} else {
				fg = BlackPixel;
				bg = WhitePixel;
			}
			if(!(Menu_DefaultCursor =
			 XCreateCursor(defaultCursorWidth, defaultCursorHeight,
			  Default_CursorBits, Default_MaskBits, defaultCursorX,
			  defaultCursorY, fg, bg, GXcopy)))
				return((Menu *)0);
		}
		menu->menuCursor = Menu_DefaultCursor;
	}
	/*
	 * Initialze the default background and border pixmaps and foreground
	 * and background colors (black and white).
	 */
	if(reverse) {
		menu->menuBgTile = BlackPixmap;
		menu->menuFgColor = WhitePixel;
		menu->menuBgColor = BlackPixel;
	} else {
		menu->menuBgTile = WhitePixmap;
		menu->menuFgColor = BlackPixel;
		menu->menuBgColor = WhitePixel;
	}
	/*
	 * Set the menu title.  If name is NULL or is an empty string, no
	 * title will be displayed.
	 */
	if(name && *name) {
		menu->menuTitleLength = strlen(menu->menuTitle = name);
		menu->menuTitleWidth = XStringWidth(name, menu->menuFontInfo,
		 0, 0);
		menu->menuItemTop = menu->menuFontInfo->height + 2 *
		 menu->menuItemPad + 1;
	} else
		menu->menuTitleLength = menu->menuTitleWidth =
		 menu->menuItemTop = 0;
	return(menu);
}

/*
 * SetItemCheck sets the check state of item "n" of the menu to "state".
 */
SetItemCheck(menu, n, state)
register Menu *menu;
register int n;
int state;
{
	register MenuItem *item;

	if(!menu || !menu->menuItems || n < 0)
		return(0);
	for(item = menu->menuItems ; n > 0 ; n--)
		if(!(item = item->nextItem))
			return(0);
	if(state)
		item->itemFlags |= itemSetChecked;
	else
		item->itemFlags &= ~itemSetChecked;
	if(((item->itemFlags & itemSetMask) >> itemSetMaskShift) !=
	 (item->itemFlags & itemStateMask)) {
		item->itemFlags |= itemChanged;
		menu->menuFlags |= menuItemChanged;
	} else
		item->itemFlags &= ~itemChanged;
	return(1);
}

/*
 * SetItemDisable sets the disable state of item "n" of the menu to "state".
 */
SetItemDisable(menu, n, state)
register Menu *menu;
register int n;
int state;
{
	register MenuItem *item;

	if(!menu || !menu->menuItems || n < 0)
		return(0);
	for(item = menu->menuItems ; n > 0 ; n--)
		if(!(item = item->nextItem))
			return(0);
	if(state)
		item->itemFlags |= itemSetDisabled;
	else
		item->itemFlags &= ~itemSetDisabled;
	if(((item->itemFlags & itemSetMask) >> itemSetMaskShift) !=
	 (item->itemFlags & itemStateMask)) {
		item->itemFlags |= itemChanged;
		menu->menuFlags |= menuItemChanged;
	} else
		item->itemFlags &= ~itemChanged;
	return(1);
}

/*
 * TrackMenu does most of the work of displaying the menu and tracking the
 * mouse.
 */
TrackMenu(menu, event)
register Menu *menu;
register XButtonPressedEvent *event;
{
	register MenuItem *item;
	register int i, button;
	register MenuItem *hilited_item = (MenuItem *)0;
	register int drawn;
	XButtonReleasedEvent ev;
	register int changed;
	int y, n, hilited_y, hilited_n, in_window;
	static MenuItem *Mouse_InItem(), *Y_InItem();
	static Unmap_Menu();

	/*
	 * Check that things are reasonable.
	 */
	if(!menu || !event || !menu->menuItems || event->type != ButtonPressed)
		return(-1);
	/*
	 * Set the changed flag and clear the menu changed flags.
	 */
	changed = menu->menuFlags & (menuChanged | menuItemChanged);
	/*
	 * If the entire menu has changed, throw away any saved pixmap and
	 * then call RecalcMenu().
	 */
	if(changed & menuChanged) {
		if(menu->menuSaved)
			XFreePixmap(menu->menuSaved);
		menu->menuSaved = (Pixmap)0;
		if(!Recalc_Menu(menu))
			return(-1);
		changed &= ~menuItemChanged;
	}
	/*
	 * Now if the window was never created, go ahead and make it.  Otherwise
	 * if the menu has changed, resize the window.
	 */
	if(!menu->menuWindow) {
		if((menu->menuWindow = XCreateWindow(RootWindow, 0, 0,
		 menu->menuWidth, menu->menuHeight, menu->menuBorderWidth,
		 Gray_Tile, menu->menuBgTile)) == (Window)0)
			return(-1);
		XDefineCursor(menu->menuWindow, menu->menuCursor);
		XSelectInput(menu->menuWindow, ExposeWindow | EnterWindow |
		 LeaveWindow | MouseMoved | ButtonReleased);
	} else if(changed & menuChanged)
	 	XChangeWindow(menu->menuWindow, menu->menuWidth,
		 menu->menuHeight);
	/*
	 * Figure out where the menu is supposed to go, from the initial button
	 * press, and move the window there.  Then map the menu.
	 */
	if(!Move_Menu(menu, event) || !Map_Menu(menu))
		return(-1);
	/*
	 * Try to grab the mouse, over a period of 10 seconds.
	 */
	for(i = 10 ; ; ) {
		if(XGrabMouse(menu->menuWindow, menu->menuCursor,
		 ButtonReleased | EnterWindow | LeaveWindow | MouseMoved))
			break;
		if(--i <= 0) {
			Unmap_Menu(menu);
			return(-1);
		}
		sleep(1);
	}
	/*
	 * Save away the button that was pressed and use it to match a
	 * corresponding ButtonReleased event.
	 */
	button = event->detail & 03;
	/*
	 * Now process events for the menu window.
	 */
	drawn = 0;
	for( ; ; ) {
		XNextEvent(&ev);
		if(ev.type != ButtonReleased && ev.window != menu->menuWindow) {
			if(menu->menuEventHandler)
				(*menu->menuEventHandler)(&ev);
			continue;
		}
		switch(ev.type) {
		 case ExposeWindow:
			/*
			 * If we have a saved pixmap, display it.  Otherwise
			 * redraw the menu and save it away.
			 */
			if(menu->menuSaved) {
				XPixmapPut(menu->menuWindow, 0, 0, 0, 0,
				 menu->menuWidth, menu->menuHeight,
				 menu->menuSaved, GXcopy, AllPlanes);
				/*
				 * If the menuItemChanged flag is still set,
				 * then we need to redraw certain menu items.
				 * ("i" is the vertical position of the top
				 * of the current item.)
				 */
				if(changed & menuItemChanged) {
					i = menu->menuItemTop;
					for(item = menu->menuItems ; item ;
					 item = item->nextItem) {
						if(item->itemFlags &
						 itemChanged)
							Modify_Item(menu, item,
							 i);
						i += item->itemHeight;
					}
				}
			} else
				Draw_Menu(menu);
			/*
			 * If the menu has changed in any way and we want to
			 * save the menu, throw away any existing save menu
			 * image and make a new one.
			 */
			XFlush();
			if(changed && (menu->menuFlags & menuSaveMenu)) {
				if(menu->menuSaved)
					XFreePixmap(menu->menuSaved);
				menu->menuSaved = XPixmapSave(menu->menuWindow,
				 0, 0, menu->menuWidth, menu->menuHeight);
			}
			/*
			 * See which item the cursor may currently be in.  If
			 * it is in a non-disabled item, hilite it.
			 */
			if(hilited_item = Mouse_InItem(menu, &hilited_y,
			 &hilited_n, &in_window))
				XPixFill(menu->menuWindow, 0, hilited_y,
				 menu->menuWidth, hilited_item->itemHeight,
				 BlackPixmap, (Bitmap)0, GXinvert, InvertPlane);
			drawn++;
			break;
		 case EnterWindow:
			in_window = TRUE;
			/* drop through */
		 case MouseMoved:
			if(!drawn || !in_window)
				break;
			/*
			 * See which item the cursor may currently be in.  If
			 * the item has changed, unhilite the old one and
			 * then hilited the new one.
			 */
			y = ((XEnterWindowEvent *)&ev)->y;
			if((item = Y_InItem(menu, &y, &n)) != hilited_item) {
				if(hilited_item)
					XPixFill(menu->menuWindow, 0,
					 hilited_y, menu->menuWidth,
					 hilited_item->itemHeight, BlackPixmap,
					 (Bitmap)0, GXinvert, InvertPlane);
				if(hilited_item = item) {
					XPixFill(menu->menuWindow, 0,
					 hilited_y = y, menu->menuWidth,
					 item->itemHeight, BlackPixmap,
					 (Bitmap)0, GXinvert, InvertPlane);
					hilited_n = n;
				}
			}
			break;
		 case LeaveWindow:
			if(!drawn)
				break;
			/*
			 * Unhilite any window that is currently hilited.
			 */
			if(hilited_item) {
				XPixFill(menu->menuWindow, 0, hilited_y,
				 menu->menuWidth, hilited_item->itemHeight,
				 BlackPixmap, (Bitmap)0, GXinvert, InvertPlane);
				hilited_item = (MenuItem *)0;
			}
			in_window = FALSE;
			break;
		 case ButtonReleased:
			/*
			 * If the correct button was released, ungrab the mouse
			 * and return the index number of any selected menu
			 * item.
			 */
			if((ev.detail & 0x3) == button) {
				if(in_window) {
					y = ((XButtonReleasedEvent *)&ev)->y;
					if((item = Y_InItem(menu, &y, &n)) !=
					 hilited_item) {
					    if(hilited_item)
						XPixFill(menu->menuWindow, 0,
						 hilited_y, menu->menuWidth,
						 hilited_item->itemHeight,
						 BlackPixmap, (Bitmap)0,
						 GXinvert, InvertPlane);
					    if(hilited_item = item) {
						XPixFill(menu->menuWindow, 0,
						 hilited_y = y, menu->menuWidth,
						 hilited_item->itemHeight,
						 BlackPixmap, (Bitmap)0,
						 GXinvert, InvertPlane);
						hilited_n = n;
					    }
					}
				}
				XUngrabMouse();
				menu->menuFlags &= ~(menuChanged |
				 menuItemChanged);
				Unmap_Menu(menu);
				XFlush();
				if(hilited_item)
					return(menu->menuInitialItem =
					 hilited_n);
				return(-1);
			}
			break;
		}
	}
}

/*
 * Recalculate all of the various menu and item variables.
 */
static Recalc_Menu(menu)
register Menu *menu;
{
	register MenuItem *item;
	register int max, i, height, fontheight;

	/*
	 * We must have already gotten the menu font.
	 */
	if(!menu->menuFontInfo)
		return(0);
	/*
	 * Initialize the various max width variables.
	 */
	fontheight = menu->menuFontInfo->height;
	height = menu->menuItemTop;
	menu->menuMaxTextWidth = menu->menuTitleWidth;
	/*
	 * The item height is the maximum of the font height and the
	 * checkbox height.
	 */
	max = fontheight;
	if(checkMarkHeight > max)
		max = checkMarkHeight;
	/*
	 * Go through the menu item list.
	 */
	for(item = menu->menuItems ; item ; item = item->nextItem) {
		/*
		 * If the item text is a single dash, we assume this is
		 * a line separator and treat it special.
		 */
		if(strcmp(item->itemText, "-") == 0)
			height += (item->itemHeight = lineSeparatorHeight);
		else {
			height += (item->itemHeight = max);
			/*
			 * Check the text width with the max value stored in
			 * menu.
			 */
			if((item->itemTextWidth = XStringWidth(item->itemText,
			 menu->menuFontInfo, 0, 0)) > menu->menuMaxTextWidth)
				menu->menuMaxTextWidth = item->itemTextWidth;
		}
		/*
		 * If the itemChanged flag is set, set the state bits.
		 */
		if(item->itemFlags & itemChanged) {
			item->itemFlags = (item->itemFlags & ~itemStateMask) |
			 ((item->itemFlags & itemSetMask) >> itemSetMaskShift);
			item->itemFlags &= ~itemChanged;
		}
	}
	/*
	 * Set the menu height and then set the menu width.
	 */
	menu->menuHeight = height;
	menu->menuWidth = 3 * menu->menuItemPad + menu->menuMaxTextWidth +
	 checkMarkWidth;
	return(1);
}

/*
 * Figure out where to popup the menu, relative to the where the button was
 * pressed.
 */
static Move_Menu(menu, ev)
register Menu *menu;
XButtonPressedEvent *ev;
{
	register MenuItem *item;
	register int n, x, y;
	int ev_x, ev_y;
	int total_width;
	Window subw;

	/*
	 * Get the coordinates of the mouse when the button was pressed.
	 */
	XInterpretLocator(RootWindow, &ev_x, &ev_y, &subw, ev->location);
	/*
	 * Try to popup the menu so that the cursor is centered within the
	 * width of the menu, but compensate if that would run it outside
	 * the display area.
	 */
	total_width = menu->menuWidth + 2 * menu->menuBorderWidth;
	if((x = ev_x - total_width / 2) < 0)
		x = 0;
	else if(x + total_width > DisplayWidth())
		x = DisplayWidth() - total_width;
#ifdef DROPMENUS
	y = 0;
#else DROPMENUS
	/*
	 * If we have an inital item, try to popup the menu centered
	 * vertically within this item.
	 */
	if(menu->menuInitialItem >= 0) {
		/*
		 * Look through the item list. "y" is the vertical position
		 * of the top of the current item and "n" is the item number.
		 */
		y = menu->menuItemTop + menu->menuBorderWidth;
		for(n = 0, item = menu->menuItems ; ; n++) {
			/*
			 * On finding the intial item, center within this item.
			 */
			if(n == menu->menuInitialItem) {
				y += item->itemHeight / 2;
				break;
			}
			y += item->itemHeight;
			/*
			 * If we run out of items, turn off the initial item
			 * and treat this as if no initial item.
			 */
			if(!(item = item->nextItem)) {
				menu->menuInitialItem = -1;
				goto noInitial;
			}
		}
	/*
	 * If no initial item, try to popup the menu centered in the item
	 * nearest the center of the menu.
	 */
	} else {
noInitial:
		/*
		 * Look through the item list. "y" is the vertical position
		 * of the top of the current item and "n" is the vertical
		 * position of the center of the menu.
		 */
		y = menu->menuItemTop + menu->menuBorderWidth;
		for(n = menu->menuHeight / 2, item = menu->menuItems ; item ;
		 item = item->nextItem)
			/*
			 * If the center of the menu is in this item, we
			 * center within this item.
			 */
			if((y += item->itemHeight) > n) {
				y -= item->itemHeight / 2;
				break;
			}
	}
#endif DROPMENU
	/*
	 * If the menu extends above outside of the display, warp
	 * the mouse vertically so the menu will all show up.
	 */
	if((y = ev_y - y) < 0) {
		XWarpMouse(RootWindow, ev_x, ev_y - y);
		y = 0;
	} else if((n = y + menu->menuHeight + 2 * menu->menuBorderWidth
		       - DisplayHeight()) > 0) {
		XWarpMouse(RootWindow, ev_x, ev_y - n);
		y -= n;
	}
	XMoveWindow(menu->menuWindow, x, y);
	/*
	 * If we are in freeze mode, save what will be the coordinates of
	 * the save image.
	 */
	if(menu->menuFlags & menuFreeze) {
		menu->menuSavedImageX = x;
		menu->menuSavedImageY = y;
	}
	return(1);
}

/*
 * Map the menu window.
 */
static Map_Menu(menu)
register Menu *menu;
{
	register int i;

	/*
	 * If we are in freeze mode, save the pixmap underneath where the menu
	 * will be (including the border).
	 */
	if(menu->menuFlags & menuFreeze) {
		XGrabServer();
		i = 2 * menu->menuBorderWidth;
		if((menu->menuSavedImage = XPixmapSave(RootWindow,
		 menu->menuSavedImageX, menu->menuSavedImageY, menu->menuWidth
		 + i, menu->menuHeight + i)) == (Pixmap)0)
			return(0);
	}
	/*
	 * Actually map the window.
	 */
	XMapWindow(menu->menuWindow);
	menu->menuFlags |= menuMapped;
	return(1);
}

/*
 * Draw the entire menu in the blank window.
 */
static Draw_Menu(menu)
register Menu *menu;
{
	register MenuItem *item;
	register int top = menu->menuItemTop;
	register int x = menu->menuItemPad;
	register int y, dim;

	/*
	 * If we have a menu title, draw it first, centered and hilited.
	 */
	if(menu->menuTitleLength) {
		XPixSet(menu->menuWindow, 0, 0, menu->menuWidth,
		 top - 1, menu->menuFgColor);
		XText(menu->menuWindow, (menu->menuWidth -
		 menu->menuTitleWidth) / 2, menu->menuItemPad, menu->menuTitle,
		 menu->menuTitleLength, menu->menuFontInfo->id,
		 menu->menuBgColor, menu->menuFgColor);
	}
	/*
	 * For each item in the list, first draw any check mark and then
	 * draw the rest of it.
	 */
	for(item = menu->menuItems ; item ; item = item->nextItem) {
		SetStateFlags(item);
		dim = (item->itemFlags & itemDisabled);
		/*
		 * Draw the check mark, possibly dimmed, wherever is necessary.
		 */
		if(item->itemFlags & itemChecked) {
			XBitmapBitsPut(menu->menuWindow, x, y = top +
			 (item->itemHeight - checkMarkHeight) / 2,
			 checkMarkWidth, checkMarkHeight, dim ? Check_GrayBits :
			 Check_MarkBits, menu->menuFgColor, menu->menuBgColor,
			 (Bitmap)0, GXcopy, AllPlanes);
		}
		/*
		 * Draw the item, possibly dimmed.
		 */
		Draw_Item(menu, item, top, dim);
		top += item->itemHeight;
	}
}

/*
 * Modify the item at vertical position y.  This routine is table driven and
 * the state and set bits are each 2 bits long, contiguous, the least
 * significant bits in the flag word and with the state bits in bits 0 & 1.
 */

#define	drawCheck	0x10
#define	removeCheck	0x08
#define	dimCheck	0x04
#define	drawItem	0x02
#define	dimItem		0x01

static char Modify_Table[] = {
	0x00, 0x02, 0x08, 0x0a, 0x01, 0x00, 0x09, 0x08,
	0x10, 0x12, 0x00, 0x12, 0x15, 0x14, 0x05, 0x00
};
	
static Modify_Item(menu, item, top)
register Menu *menu;
register MenuItem *item;
int top;
{
	register int x = menu->menuItemPad;
	register int y;
	register int center = top + item->itemHeight / 2;
	register int func = Modify_Table[item->itemFlags &
	 (itemStateMask | itemSetMask)];

	/*
	 * If we really won't be making a change, return.
	 */
	if(func == 0)
		return;
	/*
	 * Draw the check mark if needed, possibly dimmed.
	 */
	y = center - (checkMarkHeight / 2);
	if(func & (drawCheck | dimCheck))
		XBitmapBitsPut(menu->menuWindow, x, y, checkMarkWidth,
		 checkMarkHeight, (func & dimCheck) ? Check_GrayBits :
		 Check_MarkBits, menu->menuFgColor, menu->menuBgColor,
		 (Bitmap)0, GXcopy, AllPlanes);
	/*
	 * Remove the check mark if needed.
	 */
	if(func & removeCheck)
		XTileSet(menu->menuWindow, x, y, checkMarkWidth,
		 checkMarkHeight, menu->menuBgTile);
	/*
	 * Call Draw_Item if we need to draw or dim the item.
	 */
	if((x = func & dimItem) || (func & drawItem))
		Draw_Item(menu, item, top, x);
	/*
	 * Update state flags.
	 */
	SetStateFlags(item);
}

/*
 * Draw the item (less check mark) at vertical position y.
 * Dim the item if "dim" is set.
 */
static Draw_Item(menu, item, y, dim)
register Menu *menu;
register MenuItem *item;
register int y;
int  dim;
{
	register int x = 2 * menu->menuItemPad + checkMarkWidth;
	register int center = y + item->itemHeight / 2;

	/*
	 * If the item text is a single dash, draw a separating line.
	 */
	if(strcmp(item->itemText, "-") == 0) {
		XLine(menu->menuWindow, 0, center, menu->menuWidth, center,
		 1, 1, menu->menuFgColor, GXcopy, AllPlanes);
		return;
	}
	/*
	 * Draw and/or dim the text, centered vertically.
	 */
	y = center - (menu->menuFontInfo->height / 2);
	if(dim) {
		XTileSet(menu->menuWindow, x, y, item->itemTextWidth,
		 menu->menuFontInfo->height, Gray_Tile);
		XTextPad(menu->menuWindow, x, y, item->itemText,
		 item->itemTextLength, menu->menuFontInfo->id, 0, 0,
		 menu->menuFgColor, menu->menuBgColor, menu->menuFgColor ?
		 GXand : GXor, AllPlanes);
	} else
		XText(menu->menuWindow, x, y, item->itemText,
		 item->itemTextLength, menu->menuFontInfo->id,
		 menu->menuFgColor, menu->menuBgColor);
}

/*
 * Determine which enabled menu item the mouse is currently in.  Return the
 * top position of this item and its item number.  Set inwindow to whether
 * we are or not.
 */
static MenuItem *Mouse_InItem(menu, top, n, inwindow)
register Menu *menu;
int *top, *n, *inwindow;
{
	int x, y;
	Window subw;
	static MenuItem *Y_InItem();

	/*
	 * Find out where the mouse is.  If its not in the menu window,
	 * return NULL.
	 */
	XQueryMouse(RootWindow, &x, &y, &subw);
	if(subw != menu->menuWindow) {
		*inwindow = FALSE;
		return((MenuItem *)0);
	}
	*inwindow = TRUE;
	/*
	 * Now get the coordinates relative to the menu window.
	 */
	XInterpretLocator(menu->menuWindow, &x, &y, &subw, (x << 16) | y);
	/*
	 * Call Y_InItem().
	 */
	*top = y;
	return(Y_InItem(menu, top, n));
}

/*
 * Return which enabled item the locator is in.  Also return the
 * top position of this item and its item number.  Initial y passed
 * in top.
 */
static MenuItem *Y_InItem(menu, top, n)
register Menu *menu;
int *top, *n;
{
	register MenuItem *item;
	register int t, i;
	register int y = *top;
	Window subw;

	/*
	 * Go through the item list.  "t" is the vertical position of the
	 * current item and "i" is its item number.
	 */
	t = menu->menuItemTop;
	/*
	 * If the mouse is before the first item, return.
	 */
	if(y < t)
		return((MenuItem *)0);
	for(i = 0, item = menu->menuItems ; item ; i++, item = item->nextItem) {
		/*
		 * If the y coordinate is within this menu item, then return.
		 * But don't return disable items.
		 */
		if(t + item->itemHeight > y) {
			if(item->itemFlags & itemDisabled)
				return((MenuItem *)0);
			*top = t;
			*n = i;
			return(item);
		}
		t += item->itemHeight;
	}
	/*
	 * Should never get here.
	 */
	return((MenuItem *)0);
}

/*
 * Unmap_Menu() unmaps a menu, if it is currently mapped.
 */
static Unmap_Menu(menu)
register Menu *menu;
{
	register int i;

	if(!menu || !(menu->menuFlags & menuMapped))
		return;
	if(menu->menuFlags & menuFreeze) {
		XUnmapTransparent(menu->menuWindow);
		i = 2 * menu->menuBorderWidth;
		XPixmapPut(RootWindow, 0, 0, menu->menuSavedImageX,
		 menu->menuSavedImageY, menu->menuWidth + i,
		 menu->menuHeight + i, menu->menuSavedImage,
		 GXcopy, AllPlanes);
		XFreePixmap(menu->menuSavedImage);
		XUngrabServer();
	} else
		XUnmapWindow(menu->menuWindow);
	menu->menuFlags &= ~menuMapped;
}
#endif MODEMENU
