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

/*  YAAF - Yet another application framework
 *  Copyright (C) 1997, 1998 William Edward Woody
 *  
 *  This library is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU Library
 *  General Public License as published by the Free Software
 *  Foundation; either version 2 of the License, or any
 *  later version.
 *  
 *  This library is distributed in the hope that it will be
 *  useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABIILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE. See the GNU Library General Public License for
 *  more details.
 *  
 *  You should have received a copy of the GNU Library General
 *  Public License along with this library; if not, write to the
 *  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 *  
 *  To contact the author, either e-mail me at woody@pandawave.com, 
 *	or write to
 *  
 *          William Edward Woody
 *          In Phase Consulting
 *          1545 Ard Eevin Avenue
 *          Glendale, CA 91202
 *
 *	Or visit us on the web at http://www.pandawave.com
 */

#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												*/
/*																		*/
/************************************************************************/

// @G"Construction/Destruction"

#if OPT_MACOS == 1

/*	@F"XGMenu::XGMenu" @I
 *
 *		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.
 */

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


/*	@F"XGMenu::XGMenu" @I
 *
 *		The Windows version of this class constructs this menu object
 *	from the menu handle provided.
 */

XGMenu::XGMenu(HMENU mh)
{
	long i,len;
	HMENU mpop;
	
	/*
	 *	Initialize the various arrays
	 */
	
	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


/*	@F"XGMenu::XGMenu" @I
 *
 *		The X version of this routine constructs this menu object
 *	from the resource data pointed to by ptr.
 */

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


/*	@F"XGMenu::~XGMenu" @I
 *
 *		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.
 */

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														*/
/*																		*/
/************************************************************************/

// @G"Menu Attributes"

/*	@F"XGMenu::GetLength" @I
 *
 *		Return the number of elements in this thing
 */

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

/*	@F"XGMenu::InsertItem" @I
 *
 *		This inserts a new (untitled) item into the menu. 
 */

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);
}

/*	@F"XGMenu::DeleteItem" @I
 *
 *		Delete a single item from the menu
 */

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														*/
/*																		*/
/************************************************************************/

// @G"Item Attributes"

/*	@F"XGMenu::GetText" @I
 *
 *		Get the text from this menu item. Treat null string as separator
 */

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
}

/*	@F"XGMenu::SetMenu" @I
 *
 *		Set the menu item text to the text specified
 */

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
}

/*	@F"XGMenu::GetItemID" @I
 *
 *		Get the command ID
 */

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
}

/*	@F"XGMenu::SetItemID" @I
 *
 *		Set the command ID
 */

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
}

/*	@F"XGMenu::GetSubMenu" @I
 *
 *		Get the menu which is the submenu of this menu item
 *	If one doesn't exist, this returns NULL
 */

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

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

// @G"Check/Select attributes"

/*	@F"XGMenu::SetEnabled" @I
 *
 *		Set this menu item as enabled. Used internally by YAAF to enable
 *	or disable this menu item.
 */

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
}

/*	@F"XGMenu::SetCheckmark" @I
 *
 *		Set the checkmark to the mark specified. Used internally by YAAF
 *	to check or uncheck this item.
 */

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

#if OPT_MACOS == 1
	::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 
 */

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
