/*	XGMenu.cpp
 *
 *		This is the code for handling a single menu
 */

/**********************************************************************

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#include <XConfig.h>			// must be first to optimize compile on MSVC++
#include <XMenu.h>
#include <XError.h>
#include <string.h>
#include <XDataUtil.h>
#include <ctype.h>

/************************************************************************/
/*																		*/
/*	Constructor/Destructor												*/
/*																		*/
/************************************************************************/

#if OPT_MACOS == 1

/*$	XGMenu::XGMenu
 *
 *		The Macintosh version of this constructs this menu using the
 *	menu handle provided. If the 'dup' flag is set, it indicates that
 *	this menu handle is 'duplicated'; that is, the handle should not
 *	necessarly be deleted when I'm disposed of.
 *
 *		You do NOT call this constructor yourself; it is called by the
 *	XGMenuBar object.
 */

XGMenu::XGMenu(MenuHandle mh, bool dup)
{
	long i,len;
	short cmd;
	MenuHandle mpop;
	short **cmdResource;
	
	/*
	 *	Command resource. This gives the resources that are
	 *	under my control
	 */

	cmdResource = (short **)::GetResource('MCmd',(**mh).menuID);
	Assert(cmdResource != NULL);
	len = **cmdResource;
	
	/*
	 *	Initialize the various arrays
	 */
	
	fMenuHandle = mh;
	fDuplicate = dup;
	fMenuList.SetLength(len);
	fMenuItemID.SetLength(len);
	
	for (i = 0; i < len; i++) {
		fMenuItemID[i] = (*cmdResource)[i+1];
		fMenuList[i] = NULL;
	}
	
	/*
	 *	Scan and find all the pop-up menus that are contained
	 *	inside of me
	 */
	
	for (i = 0; i < len; i++) {
		::GetItemCmd(mh,i+1,&cmd);
		if (cmd == 0x1B) {
			/*
			 *	This is a submenu. Load and install into the menu
			 *	bar if it isn't already there.
			 */
			
			::GetItemMark(mh,i+1,&cmd);
			
			/*
			 *	This gives us the ID of the menu.
			 */
			
			cmd &= 0xFF;
			mpop = ::GetMenuHandle(cmd);
			dup = true;
			if (mpop == NULL) {
				// not in menu bar. Load and install
				mpop = ::GetMenu(cmd);
				if (mpop == NULL) continue;
				::InsertMenu(mpop,-1);			// insert as popup menu
				dup = false;
			}
			
			/*
			 *	mpop contains the menu handle; wrapper it in
			 *	an XGMenu object
			 */
			
			fMenuList[i] = new XGMenu(mpop,dup);
		}
	}

	/*
	 *	Done
	 */
	
	::ReleaseResource((Handle)cmdResource);
}

#endif

#if OPT_WINOS == 1


/*$	XGMenu::XGMenu
 *
 *		The Windows version of this class constructs this menu object
 *	from the menu handle provided.
 *
 *		You do NOT call this constructor yourself; it is called by the
 *	XGMenuBar object.
 */

XGMenu::XGMenu(HMENU mh, HMENU pmh, short ix)
{
	long i,len;
	HMENU mpop;
	
	/*
	 *	Initialize the various arrays
	 */
	
	fParMenu = pmh;
	fParIndex = ix;
	fMenuHandle = mh;
	len = ::GetMenuItemCount(mh);
	fMenuList.SetLength(len);
	for (i = 0; i < len; i++) {
		fMenuList[i] = NULL;
	}
	
	fInstalled = false;
	
	/*
	 *	Scan through and construct the popup menus
	 */
	
	for (i = 0; i < len; i++) {
		mpop = ::GetSubMenu(mh,i);
		if (mpop) {
			fMenuList[i] = new XGMenu(mpop);
			fMenuList[i]->fInstalled = true;
		}
	}
}

#endif

#if OPT_XWIN == 1


/*$	XGMenu::XGMenu
 *
 *		The X version of this routine constructs this menu object
 *	from the resource data pointed to by ptr.
 *
 *		You do NOT call this constructor yourself; it is called by the
 *	XGMenuBar object.
 */

XGMenu::XGMenu(unsigned char **ptr) : fMenuData(sizeof(XGSMenuItem))
{
	XGSMenuItem item;
	unsigned char *p = *ptr;
	
	for (;;) {
		if (*p == 0) {
			p += 2;
			break;
		}
		
		if (*p == 0x80) {
			// item
			item.fItemID = NSwapWord(*((short *)(p + 2)));
			item.fAccelerator = NSwapWord(*((short *)(p + 4)));
			item.fCheckmark = false;
			item.fEnabled = false;
			
			fMenuData.Append((char *)(p+6));
			fMenuData.SetExtra(fMenuData.GetNumStrings()-1,(void *)&item);
			p += 2 + p[1];
			
			fMenuList += NULL;			
		} else if (*p == 0x81) {
			item.fItemID = 0;
			item.fAccelerator = 0;
			item.fCheckmark = false;
			item.fEnabled = false;
			
			fMenuData.Append("");
			fMenuData.SetExtra(fMenuData.GetNumStrings()-1,(void *)&item);
			p += 2;

			fMenuList += NULL;
		} else if (*p == 0x82) {
			item.fItemID = 0;
			item.fAccelerator = 0;
			item.fCheckmark = false;
			item.fEnabled = false;
			
			fMenuData.Append((char *)(p+2));
			fMenuData.SetExtra(fMenuData.GetNumStrings()-1,(void *)&item);
			p += 2 + p[1];

			fMenuList += new XGMenu(&p);
		} else break;
	}
	
	*ptr = p;
}

#endif


/*$	XGMenu::~XGMenu
 *
 *		Destructor for this thing. Note that I don't dispose of the
 *	menu (except in the case of MS Windows, when the fInstalled bit
 *	is clear) because I presume on the Macintosh menus live forever,
 *	and on Windows, they are destroyed elsewhere.
 *
 *		You do NOT call this destructor yourself; it is called by the
 *	XGMenuBar object.
 */

XGMenu::~XGMenu()
{
	int i,len;
	
	len = GetLength();
	for (i = 0; i < len; i++) {
		if (fMenuList[i]) {
			delete fMenuList[i];
		}
	}
	
#if OPT_MACOS == 1
	/* Delete from menu bar and kill me. */
	if (fMenuHandle && !fDuplicate) {
		::DeleteMenu((**fMenuHandle).menuID);
		::ReleaseResource((Handle)fMenuHandle);
	}
#endif
	
#if OPT_WINOS == 1
	if (!fInstalled) ::DestroyMenu(fMenuHandle);
#endif
}


/************************************************************************/
/*																		*/
/*	Menu Attributes														*/
/*																		*/
/************************************************************************/

/*$	XGMenu::GetLength
 *
 *		Return the number of menu items and separator bars contained
 *	in this menu. This returns the total length of items in this menu,
 *	which can then be accessed by the various "insert", "delete", or
 *	other items.
 */

long XGMenu::GetLength(void) const
{
	return fMenuList.Length();
}

/*$	XGMenu::InsertItem
 *
 *		This inserts a new (untitled) item into the menu. If the index
 *	is 0, this is inserted at the start of the menu. If it is equal
 *	to GetLength(), the menu item is appended to the end.
 *
 *		Once you create a new menu item, you should rename it and give
 *	it a command code in order to use it.
 */

void XGMenu::InsertItem(long index)
{

#if OPT_MACOS == 1
	::InsertMenuItem(fMenuHandle,"\pUntitled",index);
	
	fMenuItemID.Insert(index,-1);
#endif

#if OPT_WINOS == 1
	::InsertMenu(fMenuHandle,
				 index,
				 MF_BYPOSITION | MF_STRING,
				 -1,
				 "Untitled");
#endif

#if OPT_XWIN == 1
	XGSMenuItem item;
	
	item.fItemID = 0;
	item.fAccelerator = 0;
	item.fCheckmark = 0;
	item.fEnabled = 0;
	
	fMenuData.Insert(index,"Untitled");
	fMenuData.SetExtra(index,(void *)&item);
#endif

	fMenuList.Insert(index,NULL);
}

/*$	XGMenu::DeleteItem
 *
 *		Delete a single item from the menu. This deletes the item
 *	or separator bar from the menu. The index is zero-based.
 *
 *		It is undefined what happens when you delete a hierarchical
 *	pop-up menu from the menu bar. As hierarchical menus are not
 *	currently supported on X Windows, this may or may not matter...
 */

void XGMenu::DeleteItem(long index)
{

#if OPT_MACOS == 1
	::DeleteMenuItem(fMenuHandle,index+1);
	
	fMenuItemID.Delete(index);
#endif

#if OPT_WINOS == 1
	::DeleteMenu(fMenuHandle,index,MF_BYPOSITION);
#endif

#if OPT_XWIN == 1
	fMenuData.Delete(index);
#endif

	if (fMenuList[index]) {
		delete fMenuList[index];
	}
	fMenuList.Delete(index);
}

/************************************************************************/
/*																		*/
/*	Item Attributes														*/
/*																		*/
/************************************************************************/

/*$	XGMenu::GetText
 *
 *		Get the text from this menu item. If this item is a separator bar,
 *	this returns an empty string. That implies, by the way, that a menu
 *	item cannot be an empty string.
 */

void XGMenu::GetText(long index, char *text) const
{

#if OPT_MACOS == 1
	::GetMenuItemText(fMenuHandle,index+1,(unsigned char *)text);
	if (*text == '-') *text = 0;
	else ::p2cstr((unsigned char *)text);
#endif

#if OPT_WINOS == 1
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(mii);
	mii.fMask = MIIM_DATA | MIIM_TYPE;
	mii.cch = 256;
	mii.dwTypeData = text;
	::GetMenuItemInfo(fMenuHandle,index,true,&mii);
	
	if (mii.fType & MFT_SEPARATOR) {
		*text = 0;
	}
#endif

#if OPT_XWIN == 1
	fMenuData.GetString(index,text);
#endif
}

/*$	XGMenu::SetMenu
 *
 *		Set the menu item text to the text specified. If this is
 *	set to the empty string, this creates a separator bar. Note that
 *	on the Macintosh, all menu items starting with a dash '-' will
 *	create a separator bar; this is a limitation of the Macintosh OS.
 *	For maximum compatability, you should first screen the strings
 *	to make sure you are not inadvertenly setting this to an empty
 *	string or a string staring with a dash.
 */

void XGMenu::SetText(long index, const char *text)
{

#if OPT_MACOS == 1
	char buffer[256];
	
	if (*text == 0) strcpy(buffer,"-");
	else strcpy(buffer,text);
	::SetMenuItemText(fMenuHandle,index+1,c2pstr(buffer));
#endif

#if OPT_WINOS == 1
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(mii);
	mii.fMask = MIIM_DATA | MIIM_TYPE;
	if (*text == 0) mii.fType = MFT_SEPARATOR;
	else mii.fType = MFT_STRING;
	mii.dwTypeData = const_cast<char *>(text);
	::SetMenuItemInfo(fMenuHandle,index,true,&mii);
#endif

#if OPT_XWIN == 1
	fMenuData.SetString(index,text);
#endif
}

/*$	XGMenu::GetItemID
 *
 *		Get the command ID. This returns the 16-bit command ID
 *	associated with this menu item.
 */

short XGMenu::GetItemID(long index) const
{

#if OPT_MACOS == 1
	return fMenuItemID[index];
#endif

#if OPT_WINOS == 1
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(mii);
	mii.fMask = MIIM_ID;
	::GetMenuItemInfo(fMenuHandle,index,true,&mii);
	return mii.wID;
#endif

#if OPT_XWIN == 1
	XGSMenuItem item;
	
	fMenuData.GetExtra(index,(void *)&item);
	return item.fItemID;
#endif
}

/*$	XGMenu::SetItemID
 *
 *		Set the command ID. This allows you to set the 16-bit
 *	command ID associated with this menu item to the one specified.
 *	This is useful when you are dynamically updating the contents
 *	of a menu.
 */

void XGMenu::SetItemID(long index, short id)
{

#if OPT_MACOS == 1
	fMenuItemID[index] = id;
#endif

#if OPT_WINOS == 1
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(mii);
	mii.fMask = MIIM_ID;
	mii.wID = id;
	::SetMenuItemInfo(fMenuHandle,index,true,&mii);
#endif

#if OPT_XWIN == 1
	XGSMenuItem item;
	
	fMenuData.GetExtra(index,(void *)&item);
	item.fItemID = id;
	fMenuData.SetExtra(index,(void *)&item);
#endif
}

/*$	XGMenu::GetSubMenu
 *
 *		Get the menu which is the submenu of this menu item
 *	If one doesn't exist, this returns NULL. Note that currently
 *	hierarchical menus are not supported by the X Windows version
 *	of YAAF.
 */

XGMenu *XGMenu::GetSubMenu(long index) const
{
	return fMenuList[index];
}

/************************************************************************/
/*																		*/
/*	Menu attributes														*/
/*																		*/
/************************************************************************/

/*$	XGMenu::SetEnabled
 *
 *		Set this menu item as enabled. Used internally by YAAF to enable
 *	or disable this menu item.
 *
 *	You should not normally call this routine; instead, you should respond 
 *	to the appropriate event sent to the current focus or one of it's parents.
 */

void XGMenu::SetEnabled(long index, bool enable)
{

#if OPT_MACOS == 1
	if (enable) {
		::EnableItem(fMenuHandle,index+1);
	} else {
		::DisableItem(fMenuHandle,index+1);
	}
#endif

#if OPT_WINOS == 1
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(mii);
	mii.fMask = MIIM_STATE;
	::GetMenuItemInfo(fMenuHandle,index,true,&mii);

	mii.cbSize = sizeof(mii);
	mii.fMask = MIIM_STATE;
	
	mii.fState &= ~(MFS_ENABLED | MFS_GRAYED | MFS_DISABLED);
	mii.fState |= enable ? MFS_ENABLED : MFS_GRAYED;
	::SetMenuItemInfo(fMenuHandle,index,true,&mii);
#endif

#if OPT_XWIN == 1
	XGSMenuItem item;
	
	fMenuData.GetExtra(index,(void *)&item);
	item.fEnabled = enable;
	fMenuData.SetExtra(index,(void *)&item);
#endif
}

/*$	XGMenu::EnableMenu
 *
 *		This enables or disables the entire menu.
 */

bool XGMenu::EnableMenu(bool flag)
{
#if OPT_MACOS == 1
	bool oflag;
	
	oflag = ((**fMenuHandle).enableFlags & 1) ? true : false;
	if (oflag != flag) {
		if (flag) {
			::EnableItem(fMenuHandle,0);
		} else {
			::DisableItem(fMenuHandle,0);
		}
		return true;
	} else {
		return false;
	}
#endif

#if OPT_WINOS == 1
	bool oflag;
	MENUITEMINFO mii;

	/*
	 *	Note: the symbol MFS_ENABLED is seto to 0; that means if I
	 *	want to know if a menu is enabled, I need to check the
	 *	disabled flag instead.
	 */
	
	if (fParMenu == NULL) return false;
	
	mii.cbSize = sizeof(mii);
	mii.fMask = MIIM_STATE;
	::GetMenuItemInfo(fParMenu,fParIndex,true,&mii);

	oflag = (mii.fState & (MFS_GRAYED | MFS_DISABLED)) ? false : true;
	if (oflag != flag) {
		mii.cbSize = sizeof(mii);
		mii.fMask = MIIM_STATE;
		mii.fState &= ~(MFS_GRAYED | MFS_DISABLED);
		mii.fState |= flag ? MFS_ENABLED : MFS_GRAYED;
		::SetMenuItemInfo(fParMenu,fParIndex,true,&mii);
		return true;
	} else {
		return false;
	}
#endif

#if OPT_XWIN == 1
	return false;
#endif
}

/*$	XGMenu::SetCheckmark
 *
 *		Set the checkmark to the mark specified. Used internally by YAAF
 *	to check or uncheck this item.
 *
 *	You should not normally call this routine; instead, you should respond 
 *	to the appropriate event sent to the current focus or one of it's parents.
 */

void XGMenu::SetCheckmark(long index, short mark)
{

#if OPT_MACOS == 1
	short cmd;
	
	::GetItemCmd(fMenuHandle,index+1,&cmd);
	if (cmd != 0x1B) ::SetItemMark(fMenuHandle,index+1,mark);
#endif

#if OPT_WINOS == 1
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(mii);
	mii.fMask = MIIM_STATE;
	::GetMenuItemInfo(fMenuHandle,index,true,&mii);

	mii.cbSize = sizeof(mii);
	mii.fMask = MIIM_STATE;
	
	mii.fState &= ~(MFS_CHECKED | MFS_UNCHECKED);
	mii.fState |= mark ? MFS_CHECKED : MFS_UNCHECKED;
	::SetMenuItemInfo(fMenuHandle,index,true,&mii);
#endif

#if OPT_XWIN == 1
	XGSMenuItem item;
	
	fMenuData.GetExtra(index,(void *)&item);
	item.fCheckmark = mark;
	fMenuData.SetExtra(index,(void *)&item);
#endif
}


/************************************************************************/
/*																		*/
/*	Accelerator Support													*/
/*																		*/
/************************************************************************/

#if OPT_XWIN

/*	XGMenu::GetCommand
 *
 *		Get the command by it's accelerator. This is only defined for
 *	Microsoft Windows, and is used internally.
 */

short XGMenu::GetCommand(char accel)
{
	long len;
	long i;
	XGSMenuItem item;
	
	len = GetLength();
	for (i = 0; i < len; i++) {
		if (fMenuList[i] == NULL) {
			fMenuData.GetExtra(i,(void *)&item);
			if (!item.fEnabled) continue;			// skip unenabled
			if (toupper(item.fAccelerator) == toupper(accel)) {
				return item.fItemID;
			}
		} else {
			short cmd;
			
			cmd = fMenuList[i]->GetCommand(accel);
			if (cmd != -1) return cmd;
		}
	}
	return -1;
}

#endif
