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

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

#endif

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

// @G"Construction/Destruction"

#if OPT_MACOS == 1

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

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

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

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

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

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

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

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

// @G"Top Level Access"

/*	@F"XGMenuBar::GetAccelerator" @I
 *
 *		Get the accelerator key (menu key shortcut) for this command.
 *	On Microsoft Windows this is a global, but on the Macintosh this
 *	is stored in the menu handle itself.
 */

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

}

/*	@F"XGMenuBar::SetAccelerator" @I
 *
 *		Set the accelerator key
 */

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

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

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;
	Handle h;
	
	/*
	 *	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);
	}
	::ReleaseResource((Handle)mbar);
	
	/*
	 *	Now install this menu bar and release the MBAR resource
	 */
	
	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);
		fMenuBar[i]->fInstalled = true;
	}
	
	/*
	 *	Insert into the current window
	 */
	
	if (fMDIFlag) {
		::SendMessage(fWindow,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.
 */

void XGMenuBar::UpdateMenu(XGMenu *m, XGFocus *f)
{
	long i,len;
	XGSMenuStatus ms;
	XGMenu *pm;
	short id;
	
	/*
	 *	Run through each menu item, querying the status and
	 *	setting or checking this menu as appropriate.
	 */
	
	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->SendDispatch(KXGGetMenuStatus,id,(void *)&ms);
		}
		
		m->SetEnabled(i,ms.enable);
		if (ms.check) {
			if (!ms.mark) ms.mark = KXGCheckmark;
			m->SetCheckmark(i,ms.mark);
		} else {
			m->SetCheckmark(i,0);
		}

		/*
		 *	Recurse if needed
		 */
		
		pm = m->GetSubMenu(i);
		if (pm) {
			UpdateMenu(pm,f);
		}
	}
}

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

void XGMenuBar::UpdateMenuBar(XGFocus *focus)
{
	long i,len;
	
	/*
	 *	Recurse down, updating each individual menu
	 */
	
	len = fMenuBar.Length();
	for (i = 0; i < len; i++) UpdateMenu(fMenuBar[i],focus);
	
#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

/*	@F"XGMenuBar::MenuCommand" @I
 *
 *		The Macintosh version of this processes the Macintosh menu item.
 */

bool XGMenuBar::MenuCommand(XGFocus *f, 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 != f->SendDispatch(KXGDoMenuCommand,lo,NULL)) {
				::HiliteMenu(0);
				return true;
			}
		}
	}
	
	::HiliteMenu(0);
	return false;
}

/*	@F"XGMenuBar::MenuSelect" @I
 *
 *		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(XGFocus *f, short x, short y)
{
	Point where;
	
	where.h = x;
	where.v = y;
	return MenuCommand(f,::MenuSelect(where));
}

/*	@F"XGMenuBar::MenuKey" @I
 *
 *		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(XGFocus *f, char what)
{
	return MenuCommand(f,::MenuKey(what));
}

#endif

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

#if OPT_WINOS == 1

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

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

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

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

#endif

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

#if OPT_XWIN == 1

/*	@F"XGMenuBar::MenuDispatch" @I
 *
 *		Process the specified command on X.
 */

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

/*	@F"XGMenuBar::MenuKey" @I
 *
 *		(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(XGFocus *f, 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 == f->SendDispatch(KXGDoMenuCommand,cmd,NULL)) return false;
	return true;
}

#endif

