
/* MenuMgr_create.c */

#include <stdio.h>
#include <ctype.h>
#include <X10/Xlib.h>

#include "MenuMgr.h"

#include "bitmaps/kb10.bitmap"
#include "bitmaps/kb12.bitmap"
#include "bitmaps/check10.bitmap"
#include "bitmaps/check12.bitmap"
#include "bitmaps/left_ptr.cursor"
#include "bitmaps/left_ptr_mask.cursor"

/* default menu function */
int	(*textProc)();	/* for standard text menus */

int		mm_CanInvert = 0;
int		mm_initialized = 0;
int		mm_debug = 0;
int		mm_Cursor;
FontFamily	mm_Fonts;

static char *	mm_nextitem();

#define HASHSIZE	128		/* # entries in XAssocTable */
XAssocTable *	mm_AssocTable = NULL;
extern int	mm_TextMenu();

#define DEBUG( s,n )	if(mm_debug <= n) printf s
#define max(a,b)	((a)>(b) ? (a) : (b))

/* ---------------------------------------------------------------------- */

static int
fontchar(i)

	int	i;
{
	if( i==0 )
		return 0;
	else if( i==1 )
		return 'b';
	else if( i==2 )
		return 'i';
	else
		return 0;
}

/* ---------------------------------------------------------------------- */

int
mm_InitMenus( def_env )

	char *	def_env;
{
static char *funcname = "mm_InitMenus";
DefsPtr	d = &mm_Defs;
char *	calloc();
char	fname[64];
Bitmap	b;

	if( mm_initialized )
		return 0;
	else
	{
		/* set up default function pointer */
		textProc = mm_TextMenu;

		init_defs( def_env );

		if( ((d->fgcolor == BlackPixel) && (d->bgcolor == WhitePixel))
		|| ((d->fgcolor == WhitePixel) && (d->bgcolor == BlackPixel)) )
			mm_CanInvert = 1;
		else
			mm_CanInvert = 0;

		sprintf(fname,"%s%d%c",d->fontbase,d->fontsize,
			fontchar(d->onstyle));
		if( !(mm_Fonts.fi[d->onstyle] = XOpenFont(fname)) )
		{
			fprintf(stderr,"%s: couldn't open font %s\n",
				funcname,fname);
			return -1;
		}

		sprintf(fname,"%s%d%c",d->fontbase,d->fontsize,
			fontchar(d->offstyle));
		if( !(mm_Fonts.fi[d->offstyle] = XOpenFont(fname)) )
		{
			fprintf(stderr,"%s: couldn't open font %s\n",
				funcname,fname);
			return -1;
		}

		if(mm_Fonts.fi[d->onstyle]->height > mm_Fonts.fi[d->offstyle]->height)
			mm_Fonts.tallest = d->onstyle;
		else
			mm_Fonts.tallest = d->offstyle;

		if( XStringWidth("ABCabcM",mm_Fonts.fi[d->onstyle],0,0) >
		   XStringWidth("ABCabcM",mm_Fonts.fi[d->offstyle],0,0) )
			mm_Fonts.widest = d->onstyle;
		else	
			mm_Fonts.widest = d->offstyle;

		if( d->fontsize == 10 )
		{
			b = XStoreBitmap(kb10_width,kb10_height,kb10_bits);
			mm_Fonts.kb = XMakePixmap(b,d->fgcolor,d->bgcolor);
			mm_Fonts.rv_kb = XMakePixmap(b,d->bgcolor,d->fgcolor);
			b = XStoreBitmap(check10_width,check10_height,check10_bits);
			mm_Fonts.check = XMakePixmap(b,d->fgcolor,d->bgcolor);
			mm_Fonts.rv_check = XMakePixmap(b,d->bgcolor,d->fgcolor);
		}
		else if( d->fontsize == 12 )
		{
			b = XStoreBitmap(kb12_width,kb12_height,kb12_bits);
			mm_Fonts.kb = XMakePixmap(b,d->fgcolor,d->bgcolor);
			mm_Fonts.rv_kb = XMakePixmap(b,d->bgcolor,d->fgcolor);
			b = XStoreBitmap(check12_width,check12_height,check12_bits);
			mm_Fonts.check = XMakePixmap(b,d->fgcolor,d->bgcolor);
			mm_Fonts.rv_check = XMakePixmap(b,d->bgcolor,d->fgcolor);
		}
		else
		{
			fprintf(stderr,"%s: fontsize must be 10 or 12\n",
				funcname);
			return -1;
		}
	
		mm_AssocTable = XCreateAssocTable( HASHSIZE );
		mm_Cursor = XCreateCursor(left_ptr_width, left_ptr_height, 
			left_ptr_bits, left_ptr_mask_bits, 
			left_ptr_x_hot, left_ptr_y_hot,
			d->mousefg, d->mousebg, GXcopy);

		mm_initialized = 1;
		return 0;
	}
}

/* ---------------------------------------------------------------------- */

MenuBarPtr
mm_NewMenuBar( name, name_func, menu_func )

	char *		name;
	FUNCTION	name_func;
	FUNCTION	menu_func;
{
static char	*funcname = "mm_NewMenuBar";
char		*calloc();
MenuBarPtr	mbp = NULL;

	mbp = (MenuBarPtr) calloc(1,sizeof(MenuBar));
	mbp->mbarMagic = MBAR_MAGIC;	/* for XAssocTable */
	if( name != NULL )
		mbp->mbarName = (char *) calloc(1,strlen(name)+1);
	if( mbp->mbarName != NULL )
		strcpy(mbp->mbarName,name);
	mbp->mbarNameCallback = name_func;
	mbp->mbarMenuCallback = menu_func;
	return mbp;
}

/* ---------------------------------------------------------------------- */

int
mm_DisposeMenuBar( mbp )

	MenuBarPtr	mbp;
{
static char *funcname = "mm_DisposeMenuBar";
MenuPtr		mp, theNext;
	
	if( !mbp )
		return -1;

	for(	mp = mbp->mbarMList;
		mp;
		mp = theNext )
	{
		free( (char *) mp->menuTitle );
		theNext = mp->menuNext;
		mm_DisposeMenu( mp );
	}
	return 0;
}

/* ---------------------------------------------------------------------- */

int
mm_InsertMenu( mbp, mp, beforeID )

	MenuBarPtr	mbp;		/* the menu bar to use */
	MenuPtr	mp;			/* the menu to insert */
	int		beforeID;	/* put before this ID, or append if 0 */
{
static char *funcname = "mm_InsertMenu";
char *	realloc();
MenuPtr	cm, pm;		/* current and previous menus */
MenuPtr	tm, tpm;	/* the menu to find, the previous to it */
DefsPtr	d = &mm_Defs;
int	width, height;
int	x,y,extra,i;

	if( mbp==NULL || mp==NULL )
	{
		fprintf(stderr,
		"%s: given NULL pointer; mbar: %x mp: %x\n",
		funcname,mbp,mp);
		return -1;
	}

	++mbp->mbarMenuCount;

	/* this goes all the way through the list to check for duplicates */

	for( 	tm = tpm = pm = NULL, cm = mbp->mbarMList; 
		cm != NULL;
		pm = cm, cm = cm->menuNext )
	{
		if( cm->menuID == mp->menuID )	/* menu already in list */
			return -2;
		if( cm->menuID == beforeID )	/* found it */
		{
			tm = cm;
			tpm = pm;
		}
	}

	if( tm == NULL ) 	/* didn't find it; put at tail */
	{
		if( pm )
			pm->menuNext = mp;
		else		/* must be only one in list */
			mbp->mbarMList = mp;
	}
	else			/* we did find it; put before tm */
	{
		mp->menuNext = tm;	
		if( tpm == NULL )			/* it's the first one */
			mbp->mbarMList = mp;
		else					/* there's a previous */
			tpm->menuNext = mp;
	}

	mp->menuBar = mbp;

	/* now create the command button window, if menubar is attached */

	if( !mbp->mbarMenuWindow )
		return 0;

	mm_CountMenus(mbp);

	x=0;
	y=0;
	extra = mm_Fonts.fi[mm_Fonts.widest]->width*2;
	
	for( 	i=0,tm=mbp->mbarMList; 
		tm;
		tm=tm->menuNext,i++ ) 
	{
		if( tm==mp )
		{
			mp->menuTitleOffWidth = XStringWidth( mp->menuTitle, 
				mm_Fonts.fi[d->offstyle], 0,0 );
			mp->menuTitleOnWidth = XStringWidth( mp->menuTitle, 
				mm_Fonts.fi[d->onstyle], 0,0 );
			width = max(mp->menuTitleOffWidth,mp->menuTitleOnWidth) 
				+ extra + 2*d->intMborder;
			height = mm_Fonts.fi[mm_Fonts.tallest]->height +
				2*d->intMborder;
			mp->menuTitleWindow = XCreateTransparency( 
				mbp->mbarMenuWindow, x, y, width, height );
			XSelectInput( mp->menuTitleWindow, mm_MenuTitleEvents );
			XMakeAssoc( mm_AssocTable, 
				mp->menuTitleWindow, (char *)mp );
		}
		else
			width = max( tm->menuTitleOnWidth, tm->menuTitleOffWidth )
				+ extra + 2*d->intMborder;
		tm->menuLeftEdge = x;
		XMoveWindow( tm->menuTitleWindow, x, y );
		x += width;
		mm_CalcMenuSize( tm );
	}

	return 0;
}

/* ---------------------------------------------------------------------- */

MenuPtr
mm_NewMenu( id, title )

	int	id;
	char	*title;
{
static char	*funcname = "mm_NewMenu";
char		*calloc();
MenuPtr		mp = NULL;

	if( !mm_initialized )
		mm_InitMenus("");

	mp = (MenuPtr) calloc(1,sizeof(Menu));
	mp->menuMagic = MENU_MAGIC;
	mp->menuTitle = (char *) calloc(1,strlen(title)+1);
	strcpy(mp->menuTitle,title);
	mp->menuID = id;
	mp->menuProc = textProc;	/* default */
	mm_EnableItem( mp, 0 );		/* enable the menu */
	return mp;
}

/* ---------------------------------------------------------------------- */

int
mm_DisposeMenu( mp )

	MenuPtr	mp;
{
static char *funcname = "mm_DisposeMenu";
ItemPtr		ip, theNext;

	if( !mp )
		return -1;

	for(	ip = mp->menuItems;
		ip;
		ip = theNext )
	{
		free( (char *) ip->itemLabel );
		free( (char *) ip->itemData );
		theNext = ip->itemNext;
		free( (char *) ip );
	}
	free( (char *) mp );
	return 0;
}

/* ---------------------------------------------------------------------- */

int
mm_AppendItem( mp, str )

	MenuPtr		mp;		/* menu to add to */
	char *		str;		/* character description */
{
static char *funcname = "mm_AppendItem";
char		*calloc();
ItemPtr		itemp;
char		*rest;
ItemPtr		ip;
TextDataPtr	tdp;
char		disabled;
char *		desc;

	/* make a local copy */
	desc = calloc(1,strlen(str)+1);
	strcpy(desc,str);

	/* first, parse description and set the flags */

	rest = desc;
	while( *rest )
	{
		itemp = (ItemPtr) calloc(1,sizeof(Item));
		tdp = (TextDataPtr) calloc(1,sizeof(TextData));
		tdp->tdMagic = TEXT_MAGIC;
		itemp->itemData = (char *) tdp;
		itemp->itemMagic = ITEM_MAGIC;
		itemp->itemMenu = mp;
		itemp->itemLabel = mm_nextitem( &rest, &disabled,
			&tdp->tdMarked, &tdp->tdEquiv );

		if( itemp->itemLabel == NULL )	/* no more items */
		{
			*rest = 0;
			if( itemp )
				free( (char *) itemp );
			if( tdp )
				free( (char *) tdp );
		}
		else		/* have a new item; append to list */
		{
			++mp->menuItemCount;

			if( disabled )
				mm_DisableItem( mp, mp->menuItemCount );
			else
				mm_EnableItem( mp, mp->menuItemCount );
			
			for( 	ip=(ItemPtr) mp->menuItems;	/* goto tail */
				ip && ip->itemNext;
				ip=ip->itemNext );

			if( ip == NULL )	/* it's the first one */
				mp->menuItems = itemp;
			else			/* else, set the previous */
				ip->itemNext = itemp;

		}	/* end else */
	}		/* end while(*rest) */

	if( desc )
		free( (char *) desc );
	return 0;
}

/* ---------------------------------------------------------------------- */

static char *
mm_nextitem( str, disabled, marked, equiv )

	char 	**str;
	char	*disabled;
	char	*marked;
	char	*equiv;
{
static char *funcname = "mm_nextitem";
char	*s = *str;
char	*item;
char	*start;
char	c;

	if( !*s )	/* end of string */
		return NULL;

	while( *s && isspace(*s) )	/* strip leading whitespace */
		s++;

		/* 
			active chars are:
			! - mark item (with checkmark)
			* - disables item
			^ - next char is keyboard equivalent
			[ - start options
			] - end options
			; - separator
		*/

	*disabled = *marked = *equiv = 0;	/* set defaults */

	if( *s == '[' )			/* set any options */
	{

		for( s++; *s && *s != ']'; s++ )
		{

			switch( *s )
			{
				case '!':
					*marked=1;
					break;
				case '*':
					*disabled=1;
					break;
				case '^':
					if( *(s+1) && (c=(*(s+1)))!=']' )
					{
						if( islower(c) )
							c = toupper(c);
						*equiv = c;
						s++;
					}
					else
					{
						fprintf(stderr,
						"%s: '^k' error in %s\n",
						funcname,*str);
						return NULL;
					}
					break;
				default:
					fprintf(stderr,
					"%s: bad option '%c' in %s\n",
					funcname,*s,*str);
					return NULL;
					break;
			}	/* end switch */

		}	/* end for() */

		if( !*s || *s != ']' )
		{
			fprintf(stderr,"%s: syntax error; no ']' in %s\n",
				funcname,*str);
			return NULL;
		}
		else			/* we have a ']'; options finished */
			s++;		/* advance over bracket */

	}	/* end if */
	
	/* now just mark end of item string, reset rest of string */	
	
	while( *s && *s!='\n' && isspace(*s) )	/* skip leading white space */
		s++;				/* (but not '\n') */

	start = s;				/* save start of item */

	while( *s && *s!=';' && *s!='\n' )	/* skip to end of item */
		s++;
	if( *s )
		*s++ = '\0';
	*str = s;				/* set the start of rest */

	/* remove trailing white space from item */
	s = start + strlen(start);
	while( *(--s) && s>=start && isspace(*s) )
		*s='\0';

	item = (char *) calloc(1,strlen(start)+1);
	strcpy(item,start);
	if( *item=='-' )	/* disable dashed line items */
	{
		*disabled = 1;
		*equiv = 0;
		*marked = 0;
	}

	return item;
}

/* ---------------------------------------------------------------------- */

int
mm_PrintMenuList( mbp )

	MenuBarPtr	mbp;
{
static char *funcname = "mm_PrintMenuList";
MenuPtr		mp;
int		i;

	printf("--------------------\n");
	printf("MENULIST <%s>; found %d menus in list\n\n",
		mbp->mbarName,mbp->mbarMenuCount);

	for( 	i=1, mp=mbp->mbarMList; 
		mp;
		mp=mp->menuNext,i++ )
	{
		printf("title: <%s> \tID:%d items:%d %s\n",
			mp->menuTitle,
			mp->menuID,
			mp->menuItemCount,
			MENU_IS_ENABLED(mp) && ANY_ITEMS_ENABLED(mp) ? NULL 
				: "(disabled)" );
	}

	printf("--------------------\n");
	return 0;
}

/* ---------------------------------------------------------------------- */

int
mm_PrintItemList( mp )

	MenuPtr		mp;
{
static char *funcname = "mm_PrintItemList";
ItemPtr		ip;
TextDataPtr	tdp;
int		i;

	printf("--------------------\n");

	printf("ITEMLIST <%s>; found %d items in menu\n\n",
		mp->menuTitle,mp->menuItemCount);

	for( 	i=1,ip=mp->menuItems;
		ip;
		ip=ip->itemNext,i++ )
	{
		tdp = (TextDataPtr) ip->itemData;
		printf("itemLabel: <%s> equiv:<%c> %s %s\n",
			ip->itemLabel,
			tdp->tdEquiv ? tdp->tdEquiv : ' ',
			ITEM_IS_ENABLED(mp,i) ? NULL : "(disabled)",
			tdp->tdMarked ? "(marked)" : NULL );
	}
	return 0;
}

/* ---------------------------------------------------------------------- */

MenuPtr
mm_GetMenu( env, id, menuname )

	char *	env;		/* the program name: argv[0] */
	int	id;
	char *	menuname;
{
static char *funcname = "mm_GetMenu";
char	search[256];
char *	list;
MenuPtr	mp;

	sprintf(search,"Menu.%d.%s",id,menuname);
	list = XGetDefault( env, search );
	if( !list )
		return NULL;
	mp = mm_NewMenu( id, menuname );
	if( !mp )
		return NULL;
	mm_AppendItem( mp, list );
	return mp;
}

/* ---------------------------------------------------------------------- */

MenuBarPtr
mm_GetMenuBar( env, id, menubarname )

	char *	env;
	int	id;
	char *	menubarname;
{
static char *funcname = "mm_GetMenuBar";
char	search[256];
char *	list;			/* ptr from X; DO NOT DISTURB */
char *	rest = NULL;
MenuBarPtr	mbp;
char *	menustr;
char *	calloc();
char	d,m,e;		/* don't care; stuff for menu items */
int	menuid;
char 	menutitle[128];
MenuPtr	mp;

	sprintf(search,"MenuBar.%d.%s",id,menubarname);
	list = XGetDefault( env, search );
	if( !list )
	{
		fprintf(stderr,"%s: search for < %s.%s > failed\n",
			funcname, env, search );
		return NULL;
	}

	mbp = mm_NewMenuBar( menubarname, NULL, NULL );
	if( !mbp )
		return NULL;

	if( strlen(list) )
	{
		rest = calloc( 1, strlen(list)+1 );
		strcpy(rest,list);
	}
	else
		return mbp;
		
	while( (menustr = mm_nextitem( &rest, &d, &m, &e )) )
	{
		menuid = -1;
		*menutitle = '\0';
		sscanf( menustr, "%d.%s", &menuid, menutitle );
		if( menuid > 0 && *menutitle )
		{
			mp = mm_GetMenu( env, menuid, menutitle );
			if( mp )
				mm_InsertMenu( mbp, mp, 0 );
			else
				fprintf(stderr,
					"%s: error retrieving menu id %d: < %s >\n",
					funcname,menuid,menutitle);
		}
		else
		{
			fprintf(stderr,"%s: error in menu list at < %s >\n",
				funcname,rest );
			mm_DisposeMenuBar( mbp );
			mbp = NULL;
			break;
		}
	}

	if( rest )
		free( (char *) rest );

	return mbp;
}
