/*
**	       File: popbuttonclass.c
**	Description: BOOPSI (BGUI) popup-menu button class.
**	  Copyright: (C) Copyright 1995 Jaba Development.
**		     (C) Copyright 1995 Jan van den Baard.
**			 All Rights Reserved.
**/

#include <exec/types.h>
#include <exec/libraries.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/icclass.h>
#include <graphics/gfxmacros.h>
#include <libraries/bgui.h>

#include <clib/alib_protos.h>

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/bgui.h>
#include <proto/utility.h>

#include <string.h>

#include "popbuttonclass.h"

/*
**	Compiler stuff.
**/
#ifdef _DCC
#define SAVEDS __geta4
#define ASM
#define REG(x) __ ## x
#else
#define SAVEDS __saveds
#define ASM __asm
#define REG(x) register __ ## x
#endif

/*
**	Type-cast.
**/
#define GADGET(x)	(( struct Gadget * )x )

/*
**	OS macros.
**/
#define OS30		(( struct Library * )SysBase)->lib_Version >= 39
#define OS20		(( struct Library * )SysBase)->lib_Version < 39

/*
**	PopupButtonClass object instance data.
**/
typedef struct {
	struct PopMenu	       *pmd_MenuLabels;         /* Menu labels.                 */
	ULONG			pmd_NumMenuLabels;	/* Number of menu labels.	*/
	ULONG			pmd_PopPos;		/* Menu under the mouse.	*/
	struct Image	       *pmd_Image;		/* Image for the button.	*/
	Object		       *pmd_VectorImage;	/* Vector image for the button. */
	Object		       *pmd_CheckMark;		/* CheckMark image.		*/
	UWORD			pmd_CheckWidth;         /* Scaled checkmark width.	*/
	UWORD			pmd_CheckHeight;	/* Scaled checkmark height.	*/
	struct Window	       *pmd_PopWindow;		/* Popup window pointer.	*/
	UWORD			pmd_PopWindowWidth;	/* Width of the pop window.	*/
	UWORD			pmd_PopWindowHeight;	/* Height of the pop window.	*/
	struct TextFont        *pmd_PopFont;		/* Pop window font.		*/
	ULONG			pmd_Selected;		/* Selected menu number.	*/
	UWORD			pmd_Flags;		/* See below.			*/
} PMD;

#define PMDF_NOEVENT		(1<<0)			/* ActivateGadget() activation. */

/*
**	I need a new checkmark image which is "tighter"
**	than the built-in BGUI one.
**/
STATIC const struct VectorItem myCheckMark[] = {
	{   14,     8,	    VIF_SCALE			},
	{   1,	    4,	    VIF_MOVE | VIF_AREASTART	},
	{   4,	    7,	    VIF_DRAW			},
	{   6,	    7,	    VIF_DRAW			},
	{   12,     1,	    VIF_DRAW			},
	{   13,     1,	    VIF_DRAW			},
	{   11,     1,	    VIF_DRAW			},
	{   6,	    6,	    VIF_DRAW			},
	{   5,	    6,	    VIF_DRAW			},
	{   3,	    4,	    VIF_DRAW			},
	{   1,	    4,	    VIF_LASTITEM		},
};

/*
**	Default drawinfo pens. Just in
**	case we don't get 'm from the
**	system.
**/
STATIC UWORD DefDriPens[ 12 ] = { 0, 1, 1, 2, 1, 3, 1, 0, 2, 1, 2, 1 };

/*
**	Version checking.
**/
extern struct Library *SysBase;

/*
**	For filtering vectorclass attributes.
**	Ofcourse this array needs to be updated
**	when there are attributes added to the
**	vectorclass.
**/
STATIC ULONG VectorAttrs[] = {	VIT_VectorArray,
				VIT_BuiltIn,
				VIT_Pen,
				VIT_DriPen,
				TAG_END };

/*
**	See if we should create a vector image.
**/
STATIC ASM Object *CreateVectorImage( REG(a1) struct TagItem *attrs )
{
	Class			*class;
	struct TagItem		*clones;
	Object			*vector = NULL;

	/*
	**	Get a pointer to the vectorclass.
	**/
	if ( class = BGUI_GetClassPtr( BGUI_VECTOR_IMAGE )) {
		/*
		**	We clone the original taglist
		**	to filter out any unwanted
		**	attributes.
		**/
		if ( clones = CloneTagItems( attrs )) {
			/*
			**	Filter out any non-vectorclass
			**	attributes.
			**/
			if ( FilterTagItems( clones, VectorAttrs, TAGFILTER_AND ))
				/*
				**	Create the vector image.
				**/
				vector = NewObjectA( class, NULL, clones );
			/*
			**	Free the clones.
			**/
			FreeTagItems( clones );
		}
	}
	return( vector );
}

/*
**	Notify about an attribute change.
**/
STATIC ULONG NotifyAttrChange( Object *obj, struct GadgetInfo *gi, ULONG flags, Tag tag1, ... )
{
	return( DoMethod( obj, OM_NOTIFY, &tag1, gi, flags ));
}

/*
**	Copy the menu array.
**/
STATIC ASM BOOL CopyArray( REG(a0) PMD *pmd, REG(a1) struct PopMenu *pm )
{
	struct PopMenu	*tmp = pm;
	ULONG		 size;
	BOOL		 rc = FALSE;

	/*
	**	Count the number of entries
	**	available in the menu.
	**/
	while ( tmp->pm_Label ) {
		pmd->pmd_NumMenuLabels++;
		tmp++;
	}

	/*
	**	Compute the size of the needed
	**	allocation. One extra slot is
	**	allocated to store the terminating
	**	entry.
	**/
	size = ( pmd->pmd_NumMenuLabels + 1 ) * sizeof( struct PopMenu );

	/*
	**	Allocate enough memory to hold
	**	the copy of the array.
	**/
	if ( pmd->pmd_MenuLabels = ( struct PopMenu * )AllocVec( size, MEMF_PUBLIC )) {
		/*
		**	Copy the array.
		**/
		CopyMem( pm, pmd->pmd_MenuLabels, size );
		/*
		**	Check if the position is correct.
		**/
		if ( pmd->pmd_PopPos >= pmd->pmd_NumMenuLabels )
			pmd->pmd_PopPos = pmd->pmd_NumMenuLabels - 1;
		/*
		**	Success :)
		**/
		rc = TRUE;
	}
	return( rc );
}

/*
**	Scale the checkmark.
**/
STATIC ASM VOID ScaleCheckMark( REG(a0) PMD *pmd, REG(a1) struct RastPort *rp )
{
	UBYTE	       *refstr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,_:";

	/*
	**	Could probably use some work
	**	but it works for me.
	**/
	pmd->pmd_CheckHeight = rp->TxHeight;
	pmd->pmd_CheckWidth  = TextLength( rp, refstr, 55 ) / 55;
	pmd->pmd_CheckWidth  = pmd->pmd_CheckWidth + 6;

	SetAttrs( pmd->pmd_CheckMark, IA_Width, pmd->pmd_CheckWidth, IA_Height, pmd->pmd_CheckHeight, TAG_END );
}

/*
**	Re-compute the size of the popup window.
**/
STATIC ASM VOID ComputePopWindowSize( REG(a0) PMD *pmd, REG(a1) struct TextFont *tf )
{
	struct RastPort         rp;
	UWORD			width = 0, height = 0, tlen;
	struct PopMenu	       *labels = pmd->pmd_MenuLabels;

	/*
	**	Initialize rastport and setup the font.
	**/
	InitRastPort( &rp );
	SetFont( &rp, tf );

	ScaleCheckMark( pmd, &rp );

	/*
	**	Compute the size of the
	**	window accoording to the
	**	menu labels.
	**/
	while ( labels->pm_Label ) {
		/*
		**	Compute the width of a normal
		**	menu label.
		**/
		if ( labels->pm_Label != PMB_BARLABEL ) {
			if ( labels->pm_Flags & PMF_CHECKIT ) tlen = pmd->pmd_CheckWidth + 2;
			else				      tlen = 0;
			if (( tlen += TextLength( &rp, labels->pm_Label, strlen( labels->pm_Label ))) > width )
				width = tlen;
			height += tf->tf_YSize + 1;
		} else
			height += 6;
		/*
		**	Next...
		**/
		labels++;
	}

	/*
	**	Set values.
	**/
	pmd->pmd_PopWindowWidth  = width  + 12;
	pmd->pmd_PopWindowHeight = height + 4;

}

/*
**	Render a bar-label.
**/
STATIC ASM VOID RenderBarLabel( REG(a0) struct RastPort *rp, REG(d0) UWORD top, REG(d1) UWORD width )
{
	/*
	**	Render first line.
	**/
	SetDrPt( rp, 0x7777 );		/* %0111011101110111 */
	Move( rp, 6,	 top );
	Draw( rp, width, top );

	/*
	**	Render second line.
	**/
	SetDrPt( rp, 0xDDDD );		/* %1101110111011101 */
	Move( rp, 6,	 top + 1 );
	Draw( rp, width, top + 1 );

	SetDrPt( rp, ( UWORD )-1 );	/* %1111111111111111 */
}

/*
**	Get the y-position of the item
**	number "entry".
**/
STATIC ASM UWORD GetYPos( REG(a0) struct RastPort *rp, REG(a1) struct PopMenu *labels, REG(d0) ULONG entry )
{
	UWORD			num = 0, ypos = 2;

	/*
	**	Pass through the labels updating
	**	the Y-counter until "num" matches "entry".
	**/
	while ( labels->pm_Label && num != entry ) {
		if ( labels->pm_Label == PMB_BARLABEL ) ypos += 6;
		else					ypos += rp->TxHeight + 1;
		labels++;
		num++;
	}

	return( ypos );
}

/*
**	Render a popmenu entry.
**/
STATIC VOID RenderMenuEntry( struct RastPort *rp, PMD *pmd, UWORD *pens, ULONG num, BOOL sel, struct DrawInfo *dr )
{
	UWORD			ypos, pena, penb;
	struct PopMenu	       *label = &pmd->pmd_MenuLabels[ num ];

	/*
	**	Compute entry position.
	**/
	ypos = GetYPos( rp, pmd->pmd_MenuLabels, num );

	/*
	**	Setup the correct pens.
	**/
	if ( OS20 ) {
		pena = pens[ DETAILPEN ];
		penb = pens[ BLOCKPEN  ];
	} else {
		pena = pens[ BARDETAILPEN ];
		penb = pens[ BARBLOCKPEN  ];
	}

	/*
	**	We only backfill when we render
	**	a normal menu label.
	**/
	if ( label->pm_Label != PMB_BARLABEL ) {
		/*
		**	Backfill the menu label.
		**/
		if ( OS30 ) SetAPen( rp, sel ? pena : penb );
		else	    SetAPen( rp, penb );
		SetDrMd( rp, JAM1 );

		RectFill( rp, 4, ypos, pmd->pmd_PopWindowWidth - 5, ypos + rp->TxHeight );
	}

	/*
	**	Setup drawmode.
	**/
	if ( OS30 ) SetDrMd( rp, sel ? JAM2|INVERSVID : JAM2 );
	else	    SetDrMd( rp, JAM2 );

	/*
	**	Set the pens.
	**/
	SetAPen( rp, pena );
	SetBPen( rp, penb );

	/*
	**	Render the menu label.
	**/
	if ( label->pm_Label != PMB_BARLABEL ) {
		/*
		**	Checkable?
		**/
		if ( label->pm_Flags & PMF_CHECKIT ) {
			/*
			**	Checked?
			**/
			if ( label->pm_Flags & PMF_CHECKED ) {
				SetAttrs( pmd->pmd_CheckMark, VIT_Pen, sel ? penb : pena, TAG_END );
				DrawImageState( rp, ( struct Image * )pmd->pmd_CheckMark, 6, ypos, IDS_NORMAL, dr );
			}
			Move( rp, 8 + pmd->pmd_CheckWidth, ypos + rp->TxBaseline + 1 );
		} else
			Move( rp, 6, ypos + rp->TxBaseline + 1 );
		Text( rp, label->pm_Label, strlen( label->pm_Label ));
	} else
		RenderBarLabel( rp, ypos + 2, pmd->pmd_PopWindowWidth - 6 );

	/*
	**	Invers the entry if were running
	**	on OS 2.04 and it's selected.
	**/
	if ( OS20 && sel && ( label->pm_Label != PMB_BARLABEL )) {
		SetDrMd( rp, JAM2|COMPLEMENT );
		RectFill( rp, 4, ypos, pmd->pmd_PopWindowWidth - 5, ypos + rp->TxHeight );
	}
}

/*
**	Open the popup menu window.
**
**	Please note that if there are more items that
**	there will fit on the screen the list is clipped
**	off at the bottom.
**/
STATIC ASM ULONG OpenPopupWindow( REG(a0) PMD *pmd, REG(a1) Object *obj, REG(a2) struct gpInput *gpi, REG(d0) BOOL mouse )
{
	struct Screen		*screen = gpi->gpi_GInfo->gi_Screen;
	struct RastPort          rpt;
	UWORD			 sw, sh, wleft, wtop, wwi, wwh, *pens;
	struct IBox		*ibox;
	struct TextAttr         *tattr = NULL;
	struct TextFont         *tf;
	struct RastPort         *rp;
	ULONG			 rc = GMR_NOREUSE;

	/*
	**	Get a pointer to the pen array.
	**/
	pens = gpi->gpi_GInfo->gi_DrInfo ? gpi->gpi_GInfo->gi_DrInfo->dri_Pens : DefDriPens;

	/*
	**	Get screen dimensions.
	**/
	sw = screen->Width;
	sh = screen->Height;

	/*
	**	Get object bounds and the font
	**	we use..
	**/
	DoMethod( obj, OM_GET, BT_HitBox,   &ibox );
	DoMethod( obj, OM_GET, BT_TextAttr, &tattr );

	/*
	**	Open the font.
	**/
	if ( tattr ) {
		if ( tf = pmd->pmd_PopFont = OpenFont( tattr )) {
			/*
			**	Compute popwindow size.
			**/
			ComputePopWindowSize( pmd, tf );

			/*
			**	Setup dummy rastport.
			**/
			InitRastPort( &rpt );
			SetFont( &rpt, tf );

			/*
			**	Copy dimensions.
			**/
			wwi = pmd->pmd_PopWindowWidth;
			wwh = pmd->pmd_PopWindowHeight;

			/*
			**	Compute the popwindow
			**	position.
			**/
			if ( mouse ) {
				/*
				**	Mouse activation means that the window
				**	is opened with the first label centered
				**	under the mouse.
				**/
				wleft = screen->MouseX - ( wwi >> 1 );
				wtop  = screen->MouseY - ( 2 + GetYPos( &rpt, pmd->pmd_MenuLabels, pmd->pmd_PopPos ) + ( tf->tf_YSize >> 1 ));
			} else {
				/*
				**	Key actiation will open the window
				**	under the object.
				**/
				wleft  = ibox->Left + (( ibox->Width >> 1 ) - ( wwi >> 1 )) + gpi->gpi_GInfo->gi_Window->LeftEdge;
				wtop   = ibox->Top + ibox->Height + gpi->gpi_GInfo->gi_Window->TopEdge;
			}


			/*
			**	Open the pop window.
			**/
			pmd->pmd_PopWindow = OpenWindowTags( NULL, WA_Left,		wleft,
								   WA_Top,		wtop,
								   WA_Width,		wwi,
								   WA_Height,		wwh,
								   WA_Flags,		0L,
								   WA_IDCMP,		0L,
								   WA_Borderless,	TRUE,
								   WA_AutoAdjust,	TRUE,
								   WA_SimpleRefresh,	TRUE,
								   WA_NoCareRefresh,	TRUE,
								   WA_RMBTrap,		TRUE,
								   WA_CustomScreen,	screen,
								   TAG_END );

			/*
			**	Success?
			**/
			if ( pmd->pmd_PopWindow ) {
				/*
				**	Pick up the rastport.
				**/
				rp = pmd->pmd_PopWindow->RPort;

				/*
				**	Set the font.
				**/
				SetFont( rp, tf );

				/*
				**	Background pen.
				**/
				if ( OS20 ) sw = pens[ BLOCKPEN    ];
				else	    sw = pens[ BARBLOCKPEN ];

				/*
				**	Backfill the menu.
				**/
				SetAPen( rp, sw );
				SetDrMd( rp, JAM1 );
				RectFill( rp, 0, 0, wwi, wwh );

				/*
				**	Detail pen.
				**/
				if ( OS20 ) sw = pens[ DETAILPEN    ];
				else	    sw = pens[ BARDETAILPEN ];

				/*
				**	Render frame.
				**/
				SetAPen( rp, sw );

				Move( rp, 0, 0 );
				Draw( rp, wwi - 1, 0);
				Draw( rp, wwi - 1, wwh - 1 );
				Draw( rp, 0, wwh - 1 );
				Draw( rp, 0, 0 );
				Move( rp, 1, 0 );
				Draw( rp, 1, wwh - 1 );
				Move( rp, wwi - 2, 0 );
				Draw( rp, wwi - 2, wwh - 1 );

				/*
				**	Render the menus...
				**/
				for ( sw = 0; sw < pmd->pmd_NumMenuLabels; sw++ )
					RenderMenuEntry( rp, pmd, pens, sw, FALSE, gpi->gpi_GInfo->gi_DrInfo );

				rc = GMR_MEACTIVE;
			} else {
				CloseFont( tf );
				pmd->pmd_PopFont = NULL;
			}
		}
	}
	return( rc );
}

/*
**	Return the menu number under the mouse.
**/
STATIC ASM LONG Selected( REG(a0) PMD *pmd, REG(a1) struct RastPort *rp )
{
	WORD			mx = pmd->pmd_PopWindow->MouseX, my = pmd->pmd_PopWindow->MouseY, ypos = 2;
	struct PopMenu	       *labels = pmd->pmd_MenuLabels;
	LONG			item = 0;

	/*
	**	Mouse still in the window?
	**/
	if ( mx >= 4 && mx < ( pmd->pmd_PopWindow->Width - 4 ) && my >= 2 && my < ( pmd->pmd_PopWindow->Height - 2 )) {
		/*
		**	Start at Y-offset 2 which is
		**	where the first item is located.
		**/
		ypos = 2;

		/*
		**	Go through the labels until the
		**	mouse position is located inside
		**	the offset.
		**/
		while ( labels->pm_Label ) {
			if ( labels->pm_Label == PMB_BARLABEL ) {
				/*
				**	Is it on this barlabel?
				**/
				if ( my >= ypos && my < ( ypos + 6 ))
					/*
					**	Not selectable!
					**/
					return( ~0 );
				ypos += 6;
			} else {
				/*
				**	Is it on this item?
				**/
				if ( my >= ypos && my < ( ypos + rp->TxHeight + 1 ))
					/*
					**	Yes. Return it's number.
					**/
					return( item );
				ypos += rp->TxHeight + 1;
			}
			labels++;
			item++;
		}
	}
	return( ~0 );
}

/*
**	Find the previous selectable item.
**/
STATIC ASM ULONG PrevItem( REG(a0) PMD *pmd )
{
	ULONG		prev = pmd->pmd_Selected - 1;

	/*
	**	Are we at the top already?
	**/
	if ( ! pmd->pmd_Selected )
		return( pmd->pmd_Selected );

	/*
	**	Look up the item.
	**/
	while ( pmd->pmd_MenuLabels[ prev ].pm_Label == PMB_BARLABEL ) {
		if ( ! prev )
			break;
		prev--;
	}

	return( pmd->pmd_MenuLabels[ prev ].pm_Label == PMB_BARLABEL ? pmd->pmd_Selected : prev );
}

/*
**	Find the next selectable item.
**/
STATIC ASM ULONG NextItem( REG(a0) PMD *pmd )
{
	ULONG		next = pmd->pmd_Selected + 1;

	/*
	**	Are we at the end already?
	**/
	if ( pmd->pmd_Selected == pmd->pmd_NumMenuLabels - 1 )
		return( pmd->pmd_Selected );

	/*
	**	Look up the item.
	**/
	while ( pmd->pmd_MenuLabels[ next ].pm_Label == PMB_BARLABEL ) {
		if ( next == pmd->pmd_NumMenuLabels - 1 )
			break;
		next++;
	}

	return( pmd->pmd_MenuLabels[ next ].pm_Label == PMB_BARLABEL ? pmd->pmd_Selected : next );
}

/*
**	Mutually exclude other items.
**/
STATIC ASM VOID MutEx( REG(a0) PMD *pmd )
{
	struct PopMenu	       *labels = pmd->pmd_MenuLabels, *the_one = &pmd->pmd_MenuLabels[ pmd->pmd_Selected ];
	UWORD			i;

	/*
	**	Anything to exclude?
	**/
	if ( the_one->pm_MutualExclude ) {
		/*
		**	Maximum range which can be
		**	excluded is 32 items.
		**/
		for ( i = 0; i < 32; i++, labels++ ) {
			/*
			**	End of menu?
			**/
			if ( ! labels->pm_Label )
				break;
			/*
			**	Skip the currently selected
			**	item.
			**/
			if ( labels != the_one ) {
				/*
				**	Checkable item?
				**/
				if ( labels->pm_Flags & PMF_CHECKIT ) {
					/*
					**	Exclude bit set?
					**/
					if ( the_one->pm_MutualExclude & ( 1 << i ))
						/*
						**	Yes. Un-check it.
						**/
						labels->pm_Flags &= ~PMF_CHECKED;
				}
			}
		}
	}
}

/*
**	Compute minimum object dimensions.
**/
STATIC ASM VOID SetDimensions( REG(a0) Object *obj, REG(a1) struct grmDimensions *dim, REG(d0) UWORD mx, REG(d1) UWORD my )
{
	Object				*label = NULL, *frame = NULL;
	ULONG				 place, fh = 0, fv = 0;

	/*
	**	Including frame?
	**/
	if ( ! ( dim->grmd_Flags & GDIMF_NO_FRAME )) {
		/*
		**	Obtain frame object.
		**/
		DoMethod( obj, OM_GET, BT_FrameObject, &frame );
		/*
		**	Get width & height.
		**/
		GetAttr( FRM_FrameWidth,  frame, &fh );
		GetAttr( FRM_FrameHeight, frame, &fv );
		fh <<= 1;
		fh  += 4;
		fv <<= 1;
		fv  += 2;
	}

	/*
	**	Get object label.
	**/
	DoMethod( obj, OM_GET, BT_LabelObject, &label );
	if ( label ) {
		/*
		**	Get label position.
		**/
		DoMethod( label, OM_GET, LAB_Place, &place );

		switch ( place ) {
			case	PLACE_LEFT:
			case	PLACE_IN:
			case	PLACE_RIGHT:
				if ( my < *( dim->grmd_MinSize.Height )) my  = *( dim->grmd_MinSize.Height );
				else					 my += fv;
				break;
			case	PLACE_ABOVE:
			case	PLACE_BELOW:
				my += *( dim->grmd_MinSize.Height );
				break;
		}

		switch ( place ) {
			case	PLACE_ABOVE:
			case	PLACE_IN:
			case	PLACE_BELOW:
				if ( mx < *( dim->grmd_MinSize.Width )) mx  = *( dim->grmd_MinSize.Width );
				else					mx += fh;
				break;
			case	PLACE_LEFT:
			case	PLACE_RIGHT:
				mx += *( dim->grmd_MinSize.Width );
				break;
		}
	} else {
		mx += fh;
		my += fv;
	}

	*( dim->grmd_MinSize.Width  ) = mx;
	*( dim->grmd_MinSize.Height ) = my;
}

/*
**	Create a shiny new object.
**/
STATIC ASM ULONG PMBClassNew( REG(a0) Class *cl, REG(a2) Object *obj, REG(a1) struct opSet *ops )
{
	PMD			*pmd;
	struct TagItem		*tstate = ops->ops_AttrList, *tag;
	struct PopMenu		*pm;
	ULONG			 rc = 0L;

	/*
	**	First we let the superclass
	**	create an object.
	**/
	if ( rc = DoSuperMethodA( cl, obj, ( Msg )ops )) {
		/*
		**	Get to the instance data.
		**/
		pmd = ( PMD * )INST_DATA( cl, rc );

		/*
		**	Is this necessary?
		**/
		bzero(( char * )pmd, sizeof( PMD ));

		/*
		**	Get attributes.
		**/
		while ( tag = NextTagItem( &tstate )) {
			switch ( tag->ti_Tag ) {

				case	PMB_Image:
					pmd->pmd_Image = ( struct Image * )tag->ti_Data;
					break;

				case	PMB_MenuEntries:
					pm = ( struct PopMenu * )tag->ti_Data;
					break;

				case	PMB_PopPosition:
					pmd->pmd_PopPos = tag->ti_Data;
					break;
			}
		}

		/*
		**	If we do not have a intuition image
		**	we go for a vectorclass image.
		**/
		if ( ! pmd->pmd_Image )
			pmd->pmd_VectorImage = CreateVectorImage( ops->ops_AttrList );

		/*
		**	We _must_ have menu entries.
		**/
		if ( pm && CopyArray( pmd, pm )) {
			/*
			**	Force the correct activation flags.
			**/
			SetAttrs(( Object * )rc, GA_Immediate, FALSE, GA_RelVerify, TRUE, TAG_END );
			if ( pmd->pmd_CheckMark = BGUI_NewObject( BGUI_VECTOR_IMAGE, VIT_VectorArray, myCheckMark, IA_Left, 0, IA_Top, 0, TAG_END ))
				return( rc );
		}

		/*
		**	Otherwise we fail :(
		**/
		CoerceMethod( cl, ( Object * )rc, OM_DISPOSE );
	}
	return( NULL );
}

/*
**	Set object attributes.
**/
STATIC ASM ULONG PMBClassSet( REG(a0) Class *cl, REG(a2) Object *obj, REG(a1) struct opSet *ops )
{
	PMD			*pmd = ( PMD * )INST_DATA( cl, obj );
	struct TagItem		*tstate = ops->ops_AttrList, *tag;
	struct RastPort         *rp;
	ULONG			 rc = 0L;
	WORD			 dis = GADGET( obj )->Flags & GFLG_DISABLED;
	BOOL			 update = FALSE;

	/*
	**	Be curtious, let the superclass go
	**	first.
	**/
	rc = DoSuperMethodA( cl, obj, ( Msg )ops );

	/*
	**	Get tags.
	**/
	while ( tag = NextTagItem( &tstate )) {
		switch ( tag->ti_Tag ) {

			case	VIT_VectorArray:
			case	VIT_BuiltIn:
			case	VIT_Pen:
			case	VIT_DriPen:
				/*
				**	Only set when a vector image
				**	exists!
				**/
				if ( pmd->pmd_VectorImage ) {
					SetAttrs( pmd->pmd_VectorImage, tag->ti_Tag, tag->ti_Data, TAG_END );
					update = TRUE;
				}
				break;

			case	PMB_Image:
				/*
				**	Only changeable when a previous image
				**	exists!
				**
				**	Note that you are responsible of making
				**	sure the image fit's inside the dimensions
				**	of the one you're replacing!
				**/
				if ( pmd->pmd_Image ) {
					pmd->pmd_Image = ( struct Image * )tag->ti_Data;
					update = TRUE;
				}
				break;
		}
	}

	/*
	**	Visual update necessary?
	**/
	if ((( GADGET( obj )->Flags & GFLG_DISABLED ) != dis ) || update ) {
		/*
		**	Re-render the object.
		**/
		if ( ops->ops_GInfo ) {
			if ( rp = ObtainGIRPort( ops->ops_GInfo )) {
				DoMethod( obj, GM_RENDER, ops->ops_GInfo, rp, GREDRAW_REDRAW );
				ReleaseGIRPort( rp );
			}
		}
	}
	return( rc );
}

/*
**	Re-render the object.
**/
STATIC ASM ULONG PMBClassRender( REG(a0) Class *cl, REG(a2) Object *obj, REG(a1) struct gpRender *gpr )
{
	PMD			*pmd = ( PMD * )INST_DATA( cl, obj );
	struct RastPort         *rp = gpr->gpr_RPort;
	struct IBox		*ibox;
	static UWORD		 ghostpat[] = { 0x2222, 0x8888 };
	UWORD			*pens, xoff, yoff;
	ULONG			 rc;

	/*
	**	First the superclass. If that returns
	**	NULL we don't render.
	**/
	if ( rc = DoSuperMethodA( cl, obj, ( Msg )gpr )) {
		/*
		**	Get hitbox bounds.
		**/
		DoMethod( obj, OM_GET, BT_HitBox, &ibox );

		/*
		**	Image to render?
		**/
		if ( pmd->pmd_Image ) {
			/*
			**	Set the left and top edge.
			**/
			xoff = ( ibox->Width  >> 1 ) - ( pmd->pmd_Image->Width	>> 1 ) + ibox->Left;
			yoff = ( ibox->Height >> 1 ) - ( pmd->pmd_Image->Height >> 1 ) + ibox->Top;
			/*
			**	Render it.
			**/
			DrawImageState( rp, pmd->pmd_Image, xoff, yoff, IDS_NORMAL, gpr->gpr_GInfo->gi_DrInfo );
		} else if ( pmd->pmd_VectorImage ) {
			/*
			**	Set bounds.
			**/
			SetAttrs( pmd->pmd_VectorImage, IA_Left,   ibox->Left,
							IA_Top,    ibox->Top,
							IA_Width,  ibox->Width,
							IA_Height, ibox->Height,
							TAG_END );
			/*
			**	Render it.
			**/
			DrawImageState( rp, ( struct Image * )pmd->pmd_VectorImage, 0, 0, IDS_NORMAL, gpr->gpr_GInfo->gi_DrInfo );
		}
		/*
		**	Object disabled?
		**/
		if ( GADGET( obj )->Flags & GFLG_DISABLED ) {
			/*
			**	Get the hitbox dimensions.
			**/
			DoMethod( obj, OM_GET, BT_HitBox, &ibox );
			/*
			**	Pick up pen array.
			**/
			pens = gpr->gpr_GInfo->gi_DrInfo ? gpr->gpr_GInfo->gi_DrInfo->dri_Pens : DefDriPens;
			/*
			**	Ghost it.
			**/
			SetAfPt( gpr->gpr_RPort, ghostpat, 1 );
			SetAPen( gpr->gpr_RPort, pens[ SHADOWPEN ] );
			SetDrMd( gpr->gpr_RPort, JAM1 );
			RectFill( gpr->gpr_RPort, ibox->Left, ibox->Top, ibox->Left + ibox->Width - 1, ibox->Top + ibox->Height - 1 );
		}
	}
	return( rc );
}

/*
**	Let's go active.
**/
STATIC ASM ULONG PMBClassGoActive( REG(a0) Class *cl, REG(a2) Object *obj, REG(a1) struct gpInput *gpi )
{
	PMD			*pmd = ( PMD * )INST_DATA( cl, obj );
	UWORD			*pens;
	ULONG			 rc = GMR_NOREUSE;

	/*
	**	Disabled means that we refuse to go active.
	**/
	if ( GADGET( obj )->Flags & GFLG_DISABLED )
		return( rc );

	/*
	**	By default there's nothing selected.
	**/
	pmd->pmd_Selected = ~0;

	/*
	**	Let's open the popup window.
	**/
	if (( rc = OpenPopupWindow( pmd, obj, gpi, gpi->gpi_IEvent ? TRUE : FALSE )) == GMR_MEACTIVE ) {
		/*
		**	Activated by ActivateGadget()?
		**/
		if ( ! gpi->gpi_IEvent ) {
			/*
			**	Mark us as key-activated.
			**/
			pmd->pmd_Flags |= PMDF_NOEVENT;
			/*
			**	Pick up pen array.
			**/
			pens = gpi->gpi_GInfo->gi_DrInfo ? gpi->gpi_GInfo->gi_DrInfo->dri_Pens : DefDriPens;
			/*
			**	Pre-select the first menu.
			**/
			pmd->pmd_Selected = pmd->pmd_PopPos;
			/*
			**	Make sure we get a selectable item.
			**	This will definitly break when there are
			**	no selectable items :(
			**/
			while ( pmd->pmd_MenuLabels[ pmd->pmd_Selected ].pm_Label == PMB_BARLABEL )
				pmd->pmd_Selected++;
			/*
			**	Select the item.
			**/
			RenderMenuEntry( pmd->pmd_PopWindow->RPort, pmd, pens, pmd->pmd_Selected, TRUE, gpi->gpi_GInfo->gi_DrInfo );
		}
	}
	return( rc );
}

/*
**	Handle the user input.
**/
STATIC ASM ULONG PMBClassHandleInput( REG(a0) Class *cl, REG(a2) Object *obj, REG(a1) struct gpInput *gpi )
{
	PMD			*pmd = ( PMD * )INST_DATA( cl, obj );
	ULONG			 item, rc = GMR_MEACTIVE;
	UWORD			*pens;

	/*
	**	The keyboard control of the menu does not
	**	work correctly when you have an sunmouse/key
	**	like tool like Yak running which automatically
	**	activates the window under the mouse.
	**
	**	Intuition will automatically deactivate the
	**	object when the parent window becomes deactivated.
	**
	**	The below code is just to make sure.
	**/
	if ( ! ( gpi->gpi_GInfo->gi_Window->Flags & WFLG_WINDOWACTIVE ))
		return( GMR_NOREUSE );

	/*
	**	Pick up pen array.
	**/
	pens = gpi->gpi_GInfo->gi_DrInfo ? gpi->gpi_GInfo->gi_DrInfo->dri_Pens : DefDriPens;

	/*
	**	Mouse activated?
	**/
	if ( ! ( pmd->pmd_Flags & PMDF_NOEVENT )) {
		/*
		**	Where's the mouse.
		**/
		item = Selected( pmd, pmd->pmd_PopWindow->RPort );
		/*
		**	Did it change?
		**/
		if ( item != pmd->pmd_Selected ) {
			/*
			**	Unselect current entry and
			**	select the new one.
			**/
			if ( pmd->pmd_Selected != ~0 ) RenderMenuEntry( pmd->pmd_PopWindow->RPort, pmd, pens, pmd->pmd_Selected, FALSE, gpi->gpi_GInfo->gi_DrInfo );
			if ( item != ~0 )	       RenderMenuEntry( pmd->pmd_PopWindow->RPort, pmd, pens, item, TRUE, gpi->gpi_GInfo->gi_DrInfo );
			/*
			**	Set it up.
			**/
			pmd->pmd_Selected = item;
		}
		/*
		**	Let's see the mouse stuff...
		**/
		if ( gpi->gpi_IEvent->ie_Class == IECLASS_RAWMOUSE ) {
			switch ( gpi->gpi_IEvent->ie_Code ) {

				case	SELECTUP:
					/*
					**	Report a change to the application.
					**/
					if ( pmd->pmd_Selected != ~0 ) {
						/*
						**	Notify the selection.
						**/
						NotifyAttrChange( obj, gpi->gpi_GInfo, 0L, GA_ID, GADGET( obj )->GadgetID, PMB_MenuNumber, pmd->pmd_Selected, TAG_END );
						/*
						**	PMF_CHECKIT item?
						**/
						if ( pmd->pmd_MenuLabels[ pmd->pmd_Selected ].pm_Flags & PMF_CHECKIT ) {
							/*
							**	Toggle PMF_CHECKED bit.
							**/
							pmd->pmd_MenuLabels[ pmd->pmd_Selected ].pm_Flags ^= PMF_CHECKED;
							/*
							**	Do mutual exclusion.
							**/
							MutEx( pmd );
						}
						/*
						**	Return GMR_VERIFY. Tell intuition to
						**	discard the input event.
						**/
						rc = GMR_NOREUSE | GMR_VERIFY;
					} else
						rc = GMR_NOREUSE;
					break;

				case	MENUDOWN:
					/*
					**	Abort the mission and let
					**	intuition snap down the
					**	menus.
					**/
					rc = GMR_REUSE;
					break;
			}
		}
	} else {
		if ( gpi->gpi_IEvent->ie_Class == IECLASS_RAWKEY ) {
			switch ( gpi->gpi_IEvent->ie_Code ) {

				case	0x4C:
					/*
					**	CURSOR UP
					**/
					if (( item =  PrevItem( pmd )) != pmd->pmd_Selected ) {
						RenderMenuEntry( pmd->pmd_PopWindow->RPort, pmd, pens, pmd->pmd_Selected, FALSE, gpi->gpi_GInfo->gi_DrInfo );
						pmd->pmd_Selected = item;
						RenderMenuEntry( pmd->pmd_PopWindow->RPort, pmd, pens, pmd->pmd_Selected, TRUE, gpi->gpi_GInfo->gi_DrInfo );
					}
					break;

				case	0x4D:
					/*
					**	CURSOR DOWN
					**/
					if (( item = NextItem( pmd )) != pmd->pmd_Selected ) {
						RenderMenuEntry( pmd->pmd_PopWindow->RPort, pmd, pens, pmd->pmd_Selected, FALSE, gpi->gpi_GInfo->gi_DrInfo );
						pmd->pmd_Selected = item;
						RenderMenuEntry( pmd->pmd_PopWindow->RPort, pmd, pens, pmd->pmd_Selected, TRUE, gpi->gpi_GInfo->gi_DrInfo );
					}
					break;

				case	0x44:
					/*
					**	RETURN
					**/
				case	0x43:
					/*
					**	ENTER
					**/
					NotifyAttrChange( obj, gpi->gpi_GInfo, 0L, GA_ID, GADGET( obj )->GadgetID, PMB_MenuNumber, pmd->pmd_Selected, TAG_END );
					/*
					**	PMF_CHECKIT item?
					**/
					if ( pmd->pmd_MenuLabels[ pmd->pmd_Selected ].pm_Flags & PMF_CHECKIT ) {
						/*
						**	Toggle PMF_CHECKED bit.
						**/
						pmd->pmd_MenuLabels[ pmd->pmd_Selected ].pm_Flags ^= PMF_CHECKED;
						/*
						**	Do mutual exclusion.
						**/
						MutEx( pmd );
					}
					/*
					**	Return GMR_VERIFY. Tell intuition to
					**	discard the input event.
					**/
					rc = GMR_NOREUSE | GMR_VERIFY;
					break;

				case	0x45:
					/*
					**	ESC
					**/
					rc = GMR_NOREUSE;
					break;
			}
		}
	}
	return( rc );
}

/*
**	Go inactive...
**/
STATIC ASM ULONG PMBClassGoInActive( REG(a0) Class *cl, REG(a2) Object *obj, REG(a1) struct gpGoInactive *gpgi )
{
	PMD			*pmd = ( PMD * )INST_DATA( cl, obj );

	/*
	**	Close up shop.
	**/
	if ( pmd->pmd_PopWindow ) {
		CloseWindow( pmd->pmd_PopWindow );
		pmd->pmd_PopWindow = NULL;
	}

	if ( pmd->pmd_PopFont ) {
		CloseFont( pmd->pmd_PopFont );
		pmd->pmd_PopFont   = NULL;
	}

	/*
	**	Clear ActivateGadget() flag.
	**/
	pmd->pmd_Flags &= ~PMDF_NOEVENT;

	return( DoSuperMethodA( cl, obj, ( Msg )gpgi ));
}

/*
**	Get an attribute.
**/
STATIC ASM ULONG PMBClassGet( REG(a0) Class *cl, REG(a2) Object *obj, REG(a1) struct opGet *opg )
{
	PMD			*pmd = ( PMD * )INST_DATA( cl, obj );
	ULONG			 rc;

	/*
	**	We only know one attribute.
	**/
	if ( opg->opg_AttrID == PMB_MenuNumber ) {
		*( opg->opg_Storage ) = pmd->pmd_Selected;
		rc = 1L;
	} else
		rc = DoSuperMethodA( cl, obj, ( Msg )opg );

	return( rc );
}

/*
**	Dump the object.
**/
STATIC ASM ULONG PMBClassDispose( REG(a0) Class *cl, REG(a2) Object *obj, REG(a1) Msg msg )
{
	PMD			*pmd = ( PMD * )INST_DATA( cl, obj );

	/*
	**	If we have a vector image
	**	we dispose of it here.
	**/
	if ( pmd->pmd_VectorImage ) DisposeObject( pmd->pmd_VectorImage );
	if ( pmd->pmd_CheckMark   ) DisposeObject( pmd->pmd_CheckMark	);

	/*
	**	Free the allocated PopMenu
	**	structure array.
	**/
	if ( pmd->pmd_MenuLabels  ) FreeVec( pmd->pmd_MenuLabels );

	/*
	**	The rest is for the superclass.
	**/
	return( DoSuperMethodA( cl, obj, msg ));
}

/*
 *	They want our minimum dimensions.
 */
STATIC ASM ULONG PMBClassDimensions( REG(a0) Class *cl, REG(a2) Object *obj, REG(a1) struct grmDimensions *dim )
{
	PMD			*pmd = ( PMD * )INST_DATA( cl, obj );
	UWORD			 mx, my;
	ULONG			 frame, fw, fh;
	struct grmDimensions	 dim1 = *dim;
	struct VectorItem	*vit;

	if ( pmd->pmd_Image ) {
		/*
		**	Get minimum size of
		**	the image.
		**/
		mx = pmd->pmd_Image->Width;
		my = pmd->pmd_Image->Height;

		/*
		**	We enclose the image in the
		**	frame which means that we do
		**	the frame computation ourselves.
		**/
		dim1.grmd_Flags |= GDIMF_NO_FRAME;

		/*
		**	Let the superclass have a go at it.
		**/
		DoSuperMethodA( cl, obj, ( Msg )&dim1 );

		/*
		**	Add frame thickness.
		**/
		DoMethod( obj, OM_GET, BT_FrameObject, &frame );
		if ( frame ) {
			DoMethod(( Object * )frame, OM_GET, FRM_FrameWidth,  &fw );
			DoMethod(( Object * )frame, OM_GET, FRM_FrameHeight, &fh );
			mx += fw << 1;
			my += fh << 1;
		}

		/*
		 *	Set it.
		 */
		SetDimensions( obj, &dim1, mx, my );
		return( 1L );
	} else if ( pmd->pmd_VectorImage ) {
		/*
		**	Get minimum vector sizes.
		**/
		DoMethod( pmd->pmd_VectorImage, OM_GET, VIT_VectorArray, ( ULONG * )&vit );

		/*
		**	Scan for the sizes.
		**/
		while( 1 ) {
			/*
			**	The scaling size is considered
			**	to be the minimum object size.
			**/
			if ( vit->vi_Flags & VIF_SCALE ) {
				mx = vit->vi_x;
				my = vit->vi_y;
				break;
			}

			/*
			**	Last one?
			**/
			if ( vit->vi_Flags & VIF_LASTITEM )
				goto doSuper;

			/*
			**	Next please...
			**/
			vit++;
		}

		/*
		**	First the superclass.
		**/
		dim1.grmd_Flags |= GDIMF_NO_FRAME;
		DoSuperMethodA( cl, obj, ( Msg )&dim1 );

		/*
		**	Now add our minimum sizes.
		**/
		SetDimensions( obj, &dim1, mx, my );
		return( 1L );
	}

	doSuper:
	/*
	**	Normal button.
	**/
	return( DoSuperMethodA( cl, obj, ( Msg )dim ));
}

/*
**	Do a command on the menu.
**/
STATIC ASM ULONG PMBClassCommand( REG(a0) Class *cl, REG(a2) Object *obj, REG(a1) struct pmbmCommand *pc )
{
	PMD			*pmd = ( PMD * )INST_DATA( cl, obj );
	struct PopMenu		*label = &pmd->pmd_MenuLabels[ pc->pmbm_MenuNumber ];
	ULONG			 rc = TRUE;

	/*
	**	What do they want?
	**/
	switch ( pc->MethodID ) {

		case	PMBM_CHECK_STATUS:
			/*
			**	TRUE means checked and FALSE means
			**	not checked.
			**/
			rc = label->pm_Flags & PMF_CHECKED ? TRUE : FALSE;
			break;

		case	PMBM_CHECK_MENU:
			/*
			**	Check the menu and do
			**	mutual-exclusion.
			**/
			label->pm_Flags |= PMF_CHECKED;
			MutEx( pmd );
			break;

		case	PMBM_UNCHECK_MENU:
			/*
			**	Uncheck the menu.
			**/
			label->pm_Flags &= ~PMF_CHECKED;
			break;
	}

	return( rc );
}

/*
**	The dispatcher.
**
**	SAS Users: You should either compile this module with
**		   stack checking turned off (NOSTACKCHECK) or
**		   you must use the "__interrupt" qualifier in
**		   this routine.
**/
STATIC SAVEDS ASM ULONG PMBClassDispatch( REG(a0) Class *cl, REG(a2) Object *obj, REG(a1) Msg msg )
{
	ULONG			rc;

	/*
	**	What do we have here...
	**/
	switch ( msg->MethodID ) {

		case	OM_NEW:
			rc = PMBClassNew( cl, obj, ( struct opSet * )msg );
			break;

		case	OM_SET:
			rc = PMBClassSet( cl, obj, ( struct opSet * )msg );
			break;

		case	OM_GET:
			rc = PMBClassGet( cl, obj, ( struct opGet * )msg );
			break;

		case	OM_DISPOSE:
			rc = PMBClassDispose( cl, obj, msg );
			break;

		case	GM_RENDER:
			rc = PMBClassRender( cl, obj, ( struct gpRender * )msg );
			break;

		case	GM_GOACTIVE:
			rc = PMBClassGoActive( cl, obj, ( struct gpInput * )msg );
			break;

		case	GM_HANDLEINPUT:
			rc = PMBClassHandleInput( cl, obj, ( struct gpInput * )msg );
			break;

		case	GM_GOINACTIVE:
			rc = PMBClassGoInActive( cl, obj, ( struct gpGoInactive * )msg );
			break;

		case	GRM_DIMENSIONS:
			rc = PMBClassDimensions( cl, obj, ( struct grmDimensions * )msg );
			break;

		case	WM_KEYACTIVE:
			rc = WMKF_ACTIVATE;
			break;

		case	PMBM_CHECK_STATUS:
		case	PMBM_CHECK_MENU:
		case	PMBM_UNCHECK_MENU:
			rc = PMBClassCommand( cl, obj, ( struct pmbmCommand * )msg );
			break;

		default:
			rc = DoSuperMethodA( cl, obj, msg );
			break;
	}
	return( rc );
}

/*
**	Initialize the class.
**/
Class *InitPMBClass( void )
{
	Class			*super, *cl = NULL;

	/*
	**	Obtain the BaseClass pointer which
	**	will be our superclass.
	**/
	if ( super = BGUI_GetClassPtr( BGUI_BASE_GADGET )) {
		/*
		**	Create the class.
		**/
		if ( cl = MakeClass( NULL, NULL, super, sizeof( PMD ), 0L ))
			/*
			**	Setup dispatcher.
			**/
			cl->cl_Dispatcher.h_Entry = ( HOOKFUNC )PMBClassDispatch;
	}
	return( cl );
}

/*
**	Dispose of the class.
**/
BOOL FreePMBClass( Class *cl )
{
	return( FreeClass( cl ));
}

/************ Simple, eh? ************/
