/*	XGMenuBar.cpp
 *
 *		This is the code for handling the menu bar. This is the 
 *	encapsulation of the menu bar code
 */

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

  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 <XEvent.h>
#include <XMenu.h>
#include <XMessages.h>
#include <XError.h>

/************************************************************************/
/*																		*/
/*	Globals																*/
/*																		*/
/************************************************************************/

#if OPT_WINOS == 1

HACCEL XGMenuBar::gAccel = NULL;
bool   XGMenuBar::gAccelRes = false;
long   XGMenuBar::gAccelRef = 0;

/*	_GInstance
 *
 *		This ain't pretty.
 *
 *		I need to know the instance pointer to the instance of this
 *	application for some system calls. I assume that this is defined
 *	elsewhere for my use.
 */

extern "C" {
	extern HINSTANCE _GInstance;
	extern HWND _GMDIWindow;
}

#endif

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

#if OPT_MACOS == 1

/*$	XGMenuBar::XGMenuBar
 *
 *		Construct a menu bar. On the Macintosh this is a global
 *	resource that is actually initialized elsewhere; this only
 *	initializes itself for use with the global menubar.
 *
 *		Normally you would not call this routine; this is used
 *	internally by YAAF.
 */

XGMenuBar::XGMenuBar()
{
	/*
	 *	On the Macintosh this is a global resource. This does nothing;
	 *	I assume the menu bar manager was initialized elsewhere.
	 */
	
	fMenuBarID = -1;
	fMark = false;
}

#endif

#if OPT_WINOS == 1

/*$	XGMenuBar::XGMenuBar
 *
 *		Construction a menu bar. On Windows, my class tracks the
 *	menu bar contained in a single window object.
 *
 *		Note: It's assumed the menu bar has not already been
 *	initialized in the window passed to me.
 *
 *		Normally you would not call this routine; this is used
 *	internally by YAAF.
 */

XGMenuBar::XGMenuBar(HWND w, bool mdiFlag)
{
	/*
	 *	On Windows, my class tracks the menu bar contained in a
	 *	single window object.
	 *
	 *	NOTE: I assume the menu bar hasn't already been initialized
	 *	in the window being passed to me; if one has, I won't manage
	 *	it, but will instead ignore it.
	 */
	
	fMDIFlag = mdiFlag;
	fWindow = w;

	fMenuBarID = -1;
	fMark = false;
	gAccelRef++;
}

#endif

#if OPT_XWIN == 1

/*$	XGMenuBar::XGMenuBar
 *
 *		Construction of a menu bar. On X, I handle drawing everything
 *	myself. Here, I only maintain a backwards pointer to the window
 *	for fast processing
 *
 *		Normally you would not call this routine; this is used
 *	internally by YAAF.
 */

XGMenuBar::XGMenuBar(Window w)
{
	/*
	 *	For X, I actually draw everything myself. I only maintain
	 *	a backwards pointer to the window that this menu is in
	 *	for fast processing
	 */
	
	fWindow = w;
	fMenuBarID = -1;
	fMark = false;
}

#endif

/*$	XGMenuBar::~XGMenuBar
 *
 *		I'm going away. In all cases I assume that my going away
 *	requires little more than disposing of the memory I already
 *	allocated. The memory held by the OS for the menu bar itself
 *	is disposed elsewhere.
 *
 *		Normally you would not call this routine; this is used
 *	internally by YAAF.
 */

XGMenuBar::~XGMenuBar()
{
	long i,len;
	
	len = fMenuBar.Length();
	for (i = 0; i < len; i++) delete fMenuBar[i];
	
#if OPT_XWIN == 1
	fMenuList.DeleteAll();
#endif
	
#if OPT_WINOS == 1
	/*
	 *	Release the accelerator if the ref count hits zero
	 */
	
	gAccelRef--;
	if (gAccelRef <= 0) {
		gAccelRef = 0;
		if (gAccel) {
			if (!gAccelRes) ::DestroyAcceleratorTable(gAccel);
			gAccel = NULL;
		}
	}
#endif
}


/************************************************************************/
/*																		*/
/*	Menu Manipulation													*/
/*																		*/
/************************************************************************/

/*$	XGMenuBar::GetAccelerator
 *
 *		Get the accelerator key (menu key shortcut) for this command.
 *	This gets the accelerator key regardless of the operating system,
 *	where accelerators are either a global resource (Windows) or a
 *	local resource (Macintosh).
 */

char XGMenuBar::GetAccelerator(short cmd) const
{
#if OPT_MACOS == 1
	XGMenu *m;
	short i;
	
	m = FindByCommand(cmd,&i);
	if (m) {
		MenuHandle mh;
		
		mh = m->fMenuHandle;
		::GetItemCmd(mh,i+1,&i);
		return i;
	} else return 0;
#endif

#if OPT_WINOS == 1
	ACCEL table[256];						// assume max 256 entries
	short i,len;
	
	if (gAccel == NULL) return 0;			// not set yet
	len = ::CopyAcceleratorTable(gAccel,table,256);
	for (i = 0; i < len; i++) {
		if (table[i].cmd == cmd) return table[i].key;
	}
	return 0;
#endif

}

/*$	XGMenuBar::SetAccelerator
 *
 *		Set the accelerator key. This sets the accelerator key
 *	to the command specified, and is used for translating
 *	menu command keys into menu commands.
 */

void XGMenuBar::SetAccelerator(short cmd, char accel)
{
#if OPT_MACOS == 1
	XGMenu *m;
	short i;
	
	if (accel == 0x1B) return;			// screen stupidity.
	m = FindByCommand(cmd,&i);
	if (m) {
		MenuHandle mh;
		
		mh = m->fMenuHandle;
		::SetItemCmd(mh,i+1,accel);
	}
#endif

#if OPT_WINOS == 1
	ACCEL table[256];					// assume max 256 entries
	short i,len;
	bool mod;
	
	/*
	 *	NOTE: The windows version of this affects a <<global>>
	 *	accelerator table, which translates keyboard strokes to
	 *	a menu command.
	 *
	 *	This version is imperfect; this only knows how to set
	 *	the accelerator to <CTRL> + KEY
	 */
	
	Assert(gAccel != NULL);				// make sure I don't screw up.
	len = ::CopyAcceleratorTable(gAccel,table,256);
	
	/*
	 *	See if the command is already there; if so, replace
	 */
	
	mod = false;
	for (i = 0; i < len; i++) {
		if (table[i].cmd == cmd) {
			if (accel == 0) {
				/*
				 *	Delete this entry
				 */
				
				for (i++; i < len; i++) {
					table[i-1] = table[i];
				}
				len--;
			} else {
				/*
				 *	Replace this entry
				 */
				
				table[i].key = accel;
				table[i].fVirt = FCONTROL;
			}
			mod = true;
			break;
		}
	}
	if (accel && (i >= len) && (len < 256)) {
		/*
		 *	Insert a new entry
		 */
		
		table[len].key = accel;
		table[len].cmd = cmd;
		table[len].fVirt = FCONTROL;
		len++;
		mod = true;
	}
	
	/*
	 *	Have I modified the accelerator table? If so, rebuild it
	 */
	
	if (mod) {
		if (!gAccelRes) ::DestroyAcceleratorTable(gAccel);
		gAccel = ::CreateAcceleratorTable(table,len);
		gAccelRes = false;
	}
#endif

}

/************************************************************************/
/*																		*/
/*	Loading the Menu Bar												*/
/*																		*/
/************************************************************************/

/*$	XGMenuBar::LoadFromResource
 *
 *		This loads the menu bar from the specified resource. This
 *	then sets up the current menu bar with the specified menu. This
 *	will not act if the menu bar has already been loaded with the
 *	specified resource.
 *
 *		You must call UpdateMenuBar() after loading this thing.
 *
 *		Normally you would not call this routine; this would be
 *	called for you by YAAF.
 */

bool XGMenuBar::LoadFromResource(short resid)
{
	if (fMenuBarID == resid) return false;
	fMenuBarID = resid;
	
#if OPT_XWIN == 1
	unsigned char *mdata;
	long len,i;
	
	mdata = (unsigned char *)GetResourceData('Menu',resid);
	Assert(mdata != NULL);
	
	/*
	 *	Dispose of the old menu bar array
	 */
	
	len = fMenuBar.Length();
	for (i = 0; i < len; i++) {
		delete fMenuBar[i];
	}
	fMenuList.DeleteAll();
	
	/*
	 *	Load the new menu bar. 
	 */
	
	fMenuBar.SetLength(0);
	
	for (;;) {
		XGMenu *menu;
		
		if (*mdata == 0) break;					// end
		if (*mdata == 0x82) {
			/*
			 *	Handle menu
			 */
			
			fMenuList.Append((char *)(mdata + 2));
			mdata += 2 + mdata[1];
			menu = new XGMenu(&mdata);
			fMenuBar += menu;
		}
	}
#endif
	
#if OPT_MACOS == 1
	short **mbar;
	short i,len;
	MenuHandle mh;
	
	/*
	 *	Load the specified 'MBAR' resource. Instead of passing this
	 *	to the menu manager, I will parse it myself.
	 */
	
	mbar = (short **)::GetResource('MBAR',resid);
	Assert(mbar != NULL);
	
	/*
	 *	Dispose of the old menu bar array
	 */
	
	len = fMenuBar.Length();
	for (i = 0; i < len; i++) delete fMenuBar[i];
	
	/*
	 *	Load the new menu bar. Note the hierarchical submenus
	 *	and the command resources are loaded in the XGMenu
	 *	constructor.
	 */
	
	len = **mbar;
	fMenuBar.SetLength(len);
	for (i = 1; i <= len; i++) {
		mh = ::GetMenu((*mbar)[i]);
		Assert(mh != NULL);
		
		if (i == 1) {
			/*
			 *	First entry--insert desk accessories
			 */
			
			::AppendResMenu(mh,'DRVR');
		}

		fMenuBar[i-1] = new XGMenu(mh);
		::InsertMenu(mh, 0);
	}
	::ReleaseResource((Handle)mbar);
	::DrawMenuBar();

	/*
	 *	Now install this menu bar and release the MBAR resource
	 */
	
	/*Handle h = ::GetNewMBar(resid);
	Assert(h != NULL);
	::SetMenuBar(h);*/
	
	/*
	 *	Mark me dirty; at the next opportunity I will refresh
	 *	things.
	 */
	
	fMark = true;
#endif

#if OPT_WINOS == 1
	HMENU menu;
	HMENU smenu;
	long i,len;
	
	/*
	 *	Load the specified menu resource.
	 */
	
	menu = ::LoadMenu(_GInstance,MAKEINTRESOURCE(resid));
	Assert(menu != NULL);
	
	/*
	 *	Parse the contents of this menu bar
	 */
	
	len = fMenuBar.Length();
	for (i = 0; i < len; i++) delete fMenuBar[i];
	
	len = ::GetMenuItemCount(menu);
	fMenuBar.SetLength(len);
	for (i = 0; i < len; i++) {
		smenu = ::GetSubMenu(menu,i);
		fMenuBar[i] = new XGMenu(smenu,menu,i);
		fMenuBar[i]->fInstalled = true;
	}
	
	/*
	 *	Insert into the current window
	 */
	
	if (fMDIFlag) {
		::SendMessage(_GMDIWindow,WM_MDISETMENU,(WPARAM)menu,NULL);
	} else {
		::SetMenu(fWindow,menu);
	}
	
	/*
	 *	And mark this as true
	 */
	
	fMark = true;
	
	/*
	 *	If the accelerator table hasn't been loaded, load it here.
	 *
	 *	Yeah, this is a bit of a kludge. But this seems to be an
	 *	opportune moment to do this.
	 *
	 *	As the accelerator table is global (I presume), it's
	 *	marked as #128
	 */
	
	if (!gAccel) {
		gAccelRes = true;
		gAccel = ::LoadAccelerators(_GInstance,MAKEINTRESOURCE(128));
	}
#endif

	return true;
}

/************************************************************************/
/*																		*/
/*	Miscellaneous Support												*/
/*																		*/
/************************************************************************/

/*	XGMenuBar::FindByCommand
 *
 *		Find the menu and the index by it's command. This works by
 *	recursing down the list of menus until I hit the right one.
 */

XGMenu *XGMenuBar::FindByCommand(short cmd, short *offset) const
{
	long i,len;
	XGMenu *m;
	
	len = fMenuBar.Length();
	for (i = 0; i < len; i++) {
		m = FindByCommand(fMenuBar[i],cmd,offset);
		if (m) return m;
	}
	return NULL;
}

XGMenu *XGMenuBar::FindByCommand(XGMenu *m, short cmd, short *offset)
{
	long i,len;
	XGMenu *p;
	
	len = m->GetLength();
	for (i = 0; i < len; i++) {
		if (m->GetItemID(i) == cmd) {
			*offset = i;
			return m;
		}
	
		p = m->GetSubMenu(i);
		if (p) {
			p = FindByCommand(p,cmd,offset);
			if (p) return p;
		}
	}
	return NULL;
}

#if OPT_MACOS == 1

/*	XGMenuBar::FindByMenuID
 *
 *		This finds the XGMenu by the Macintosh's menu identifier
 */

XGMenu *XGMenuBar::FindByMenuID(short id) const
{
	long i,len;
	XGMenu *m;
	
	len = fMenuBar.Length();
	for (i = 0; i < len; i++) {
		m = FindByMenuID(fMenuBar[i],id);
		if (m) return m;
	}
	return NULL;
}

XGMenu *XGMenuBar::FindByMenuID(XGMenu *m, short id)
{
	long i,len;
	XGMenu *p;
	
	len = m->GetLength();
	for (i = 0; i < len; i++) {
		MenuHandle mh;
		
		mh = m->fMenuHandle;
		if ((**mh).menuID == id) return m;
	
		p = m->GetSubMenu(i);
		if (p) {
			p = FindByMenuID(p,id);
			if (p) return p;
		}
	}
	return NULL;
}

#endif

/************************************************************************/
/*																		*/
/*	Updating based on menu bar											*/
/*																		*/
/************************************************************************/

/*	XGMenuBar::UpdateMenu
 *
 *		Update the menu specified.
 */

bool XGMenuBar::UpdateMenu(XGMenu *m, XGDispatch *f)
{
	long i,len;
	XGSMenuStatus ms;
	XGMenu *pm;
	short id;
	bool enflag;
	bool enable;
	
	/*
	 *	Run through each menu item, querying the status and
	 *	setting or checking this menu as appropriate.
	 */
	
	enflag = false;
	len = m->GetLength();
	for (i = 0; i < len; i++) {
		ms.enable = false;
		ms.check = false;
		ms.mark = KXGCheckmark;
		
		id = m->GetItemID(i);
		if (id) {
			f->DoDispatch(KXGGetMenuStatus,id,(void *)&ms);
		}

		/*
		 *	Recurse if needed
		 */
		
		pm = m->GetSubMenu(i);
		if (pm) {
			enable = UpdateMenu(pm,f);
		} else {
			enable = false;
		}
		
		/*
		 *	Enable if the command is enabled, or if this has
		 *	a submenu with enabled items.
		 */
		
		enflag = enflag || ms.enable || enable;
		m->SetEnabled(i,ms.enable || enable);
		if (ms.check) {
			if (!ms.mark) ms.mark = KXGCheckmark;
			m->SetCheckmark(i,ms.mark);
		} else {
			m->SetCheckmark(i,0);
		}
	}
	return enflag;
}

/*	XGMenuBar::UpdateMenuBar
 *
 *		This updates the menu bar, sending commands to the specified
 *	focus.
 */

void XGMenuBar::UpdateMenuBar(XGDispatch *focus)
{
	long i,len;
	bool enflag;
	
	/*
	 *	Recurse down, updating each individual menu
	 */
	
	len = fMenuBar.Length();
	for (i = 0; i < len; i++) {
		enflag = UpdateMenu(fMenuBar[i],focus);
		fMark = fMenuBar[i]->EnableMenu(enflag) || fMark;
	}
	
#if OPT_MACOS == 1
	/*
	 *	Special case code for desk accessories in a Macintosh app.
	 *	Make sure all the desk accessories are enabled.
	 */
	
	MenuHandle mh;
	
	mh = fMenuBar[0]->fMenuHandle;
	len = ::CountMItems(mh);
	for (i = fMenuBar[0]->GetLength(); i < len; i++) {
		::EnableItem(mh,i+1);
	}
#endif
	
	/*
	 *	Now redraw the menu bar if required
	 */
	
	if (fMark) {
		fMark = false;
		
#if OPT_MACOS == 1
		::DrawMenuBar();
#endif

#if OPT_WINOS == 1
		::DrawMenuBar(fWindow);
#endif
	}
}

/************************************************************************/
/*																		*/
/*	Macintosh Menu Processing											*/
/*																		*/
/************************************************************************/

#if OPT_MACOS == 1

/*	XGMenuBar::MenuCommand
 *
 *		The Macintosh version of this processes the Macintosh menu item.
 */

bool XGMenuBar::MenuCommand(long l)
{
	short hi,lo;
	XGMenu *m;
	
	/*
	 *	Figure out the menu and item ID
	 */
	
	hi = (short)(l >> 16);
	lo = (short)(l & 0xFFFF);
	if (hi == 0) return false;
	
	/*
	 *	Now determine what the command is and send it
	 */
	
	m = FindByMenuID(hi);
	if (m) {
		if (m == fMenuBar[0]) {
			/* Desk accessory special case code */
			if (lo > m->GetLength()) {
				unsigned char buffer[64];
				MenuHandle mh;
				
				mh = m->fMenuHandle;
				::GetMenuItemText(mh,lo,buffer);
				::OpenDeskAcc(buffer);
				::HiliteMenu(0);
				return true;
			}
		}

		lo = m->GetItemID(lo-1);
		if (lo) {
			if (-1 != XGDispatch::SendDispatch(KXGDoMenuCommand,lo,NULL)) {
				::HiliteMenu(0);
				return true;
			}
		}
	}
	
	::HiliteMenu(0);
	return false;
}

/*	XGMenuBar::MenuSelect
 *
 *		The Macintosh version of this.
 *
 *		This is called when the menu has been clicked on. This calls
 *	the appropriate OS routines and dispatches to the menu clicked on
 */

bool XGMenuBar::MenuSelect(short x, short y)
{
	Point where;
	
	where.h = x;
	where.v = y;
	return MenuCommand(::MenuSelect(where));
}

/*	XGMenuBar::MenuKey
 *
 *		The Macintosh verison of this.
 *
 *		This is called when a key was pressed with the command button
 *	down. This calls the appropriate OS routine to figure out what
 *	menu was selected, and dispatches to the item selected.
 */

bool XGMenuBar::MenuKey(char what)
{
	return MenuCommand(::MenuKey(what));
}

#endif

/************************************************************************/
/*																		*/
/*	MS Windows Menu Processing											*/
/*																		*/
/************************************************************************/

#if OPT_WINOS == 1

/*	XGMenuBar::MenuCommand
 *
 *		The Windows version of this. This processes a window command
 *	when one is sent to my focus.
 */

bool XGMenuBar::MenuCommand(WPARAM cmd)
{
	if (-1 == XGDispatch::SendDispatch(KXGDoMenuCommand,cmd,NULL)) return false;
	return true;
}

/*	XGMenuBar::TranslateAccelerator
 *
 *		(Windows) Translate the accelerator on the accelrator table I'm
 *	maintaining
 */

long XGMenuBar::TranslateAccelerator(HWND w, LPMSG msg)
{
	if (gAccel) {
		return ::TranslateAccelerator(w,gAccel,msg);
	} else {
		return 0;
	}
}

#endif

/************************************************************************/
/*																		*/
/*	X Menu Processing													*/
/*																		*/
/************************************************************************/

#if OPT_XWIN == 1

/*	XGMenuBar::MenuDispatch
 *
 *		Process the specified command on X.
 */

bool XGMenuBar::MenuDispatch(short cmd)
{
	if (-1 == XGDispatch::SendDispatch(KXGDoMenuCommand,cmd,NULL)) return false;
	return true;
}

/*	XGMenuBar::MenuKey
 *
 *		(X) This is called when a key was pressed with the command button
 *	down. This calls the appropriate OS routine to figure out what
 *	menu was selected, and dispatches to the item selected.
 */

bool XGMenuBar::MenuKey(char what)
{
	long i,len;
	short cmd;
	
	len = fMenuBar.Length();
	for (i = 0; i < len; i++) {
		cmd = fMenuBar[i]->GetCommand(what);
		if (cmd != -1) break;
	}
	if (i >= len) return false;
	
	if (-1 == XGDispatch::SendDispatch(KXGDoMenuCommand,cmd,NULL)) return false;
	return true;
}

#endif

