/****** cdpanel_gc/--datasheet-- ******************************************

   NAME
	   cdpanel.gadget -- CD front panel

   SUPERCLASS
	   gadgetclass

   DESCRIPTION

   METHODS
	   GM_DOMAIN
	   GM_RENDER
	   GM_HITTEST
	   GM_GOACTIVE
	   GM_GOINACTIVE
	   OM_SET
	   OM_UPDATE
	   OM_GET
	   OM_NEW
	   OM_DISPOSE

	   All other methods are passed to the superclass.

   ATTRIBUTES

   NOTES

   SEE ALSO

******************************************************************************

*/

#include "sysheaders.h"
#include "classdata.h"
#include "cdpanel.h"

static ULONG new_method( Class *cl, Object *o, struct opSet *ops);
static ULONG dispose_method( Class *cl, Object *o, Msg msg);
static ULONG set_method( Class *cl, Object *o, struct opSet *ops);
static ULONG get_method( Class *cl, Object *o, struct opGet *opg);
static ULONG render_method( Class *cl, Object *o, struct gpRender *gpr);
static ULONG domain_method( Class *cl, Object *o, struct gpDomain *gpd);

/* handy macro for handling tag data */
#define SETTAGARG(tag, id, data)	{ tag.ti_Tag = (ULONG)(id);\
					  tag.ti_Data = (ULONG)(data); }

/* prototype for external function from 'gadgetbox.asm' */
extern __stdargs void SetupGadgetIBox(
									 struct Gadget *gadget,
									 struct IBox *domain,
									 struct IBox *box);
										 
/* prototype for LayoutClass */
static ULONG __saveds __asm CDPanelDispatch(
											register __a0 Class * class,
											register __a2 Object * object,
											register __a1 Msg message);

void Sprintf(char *, const char *, ...);

static struct ClassLibrary *BevelBase;

ULONG
SendNotify(Object * o, struct GadgetInfo *gi, ULONG flags, Tag t,...)
{
	return DoMethod(o, OM_NOTIFY, &t, gi, flags);
}

static ULONG __asm /* no __saveds necessary, this is called through the dispatcher anyway */
tagset(
		  register __a0 Class * cl,
		  register __a2 Object * o,
		  register __a1 struct opSet *ops)
{
	struct CDPanelData *sd = INST_DATA(cl, o);
	register ULONG tidata;
	register BOOL ours;
	BOOL refresh = FALSE;
	struct TagItem *tag;
	struct TagItem *tstate = ops->ops_AttrList;

	if (sd)
	{
		while (tag = NextTagItem(&tstate))
		{
			tidata = tag->ti_Data;
			ours = TRUE;

			switch (tag->ti_Tag)
			{
				case CDPANEL_Artist:
					sd->gad_Data[Artist] = tidata;
					sd->gad_Old[Artist] = ~0;
					break;
					
				case CDPANEL_Title:
					sd->gad_Data[Title] = tidata;
					sd->gad_Old[Title] = ~0;
					break;
					
				case CDPANEL_TrackTitles:
					sd->gad_TrackTitles = (STRPTR *)tidata;
					break;
					
				case CDPANEL_TotalTime:
					sd->gad_Data[TotalTime] = tidata;
					sd->gad_Old[TotalTime] = ~0;
					break;
					
				case CDPANEL_Time:
					sd->gad_Data[Time] = tidata;
					sd->gad_Old[Time] = ~0;
					break;
					
				case CDPANEL_Tracks:
					sd->gad_Data[Tracks] = tidata;
					sd->gad_Old[Tracks] = ~0;
					break;
					
				case CDPANEL_TrackTimes:
					sd->gad_TrackTimes = (ULONG *)tidata;
					break;
					
				case CDPANEL_Track:
					sd->gad_Old[Track] = sd->gad_Data[Track];
					sd->gad_Data[Track] = tidata;
					break;
					
				case CDPANEL_TrackTime:
					sd->gad_Data[TrackTime] = tidata;
					sd->gad_Old[TrackTime] = ~0;
					break;
					
				case CDPANEL_Status:
					if (tidata == CDP_EMPTY || tidata == CDP_EJECTED)
					{
						memset(sd->gad_Data, 0, sizeof(sd->gad_Data));
						memset(sd->gad_Graph, sd->gad_BgPen, 320);
					}
					else
					if (tidata == CDP_STOPPED)
					{
						sd->gad_Data[Track] = 0;
						sd->gad_Data[TrackTime] = 0;
						sd->gad_Data[Time] = 0;
						memset(sd->gad_Graph, sd->gad_BgPen, 320);
					}
					sd->gad_Data[Status] = tidata;
				//	sd->gad_Old[Status] = ~0;
					break;
					
				case CDPANEL_BgPen:
					sd->gad_BgPen = (WORD)tidata;
					break;
					
				case CDPANEL_FgPen:
					sd->gad_FgPen = (WORD)tidata;
					break;
					
				case CDPANEL_NoBorder:
					sd->gad_NoBorder = (BOOL)tidata;
					break;
					
				case GA_TextAttr:
					sd->gad_Font = OpenFont((struct TextAttr *)tidata);
					break;
					
				default:
					ours = FALSE;
					break;
			}
			if (ours)
				refresh = TRUE;
		}
		
		if (ops->ops_GInfo && refresh)
		{
			struct RastPort *rp;
	
			if (rp = ObtainGIRPort(ops->ops_GInfo))
			{
				DoMethod(o, GM_RENDER, ops->ops_GInfo, rp, GREDRAW_UPDATE);
				ReleaseGIRPort(rp);
				refresh = FALSE;
			}
		}
	}
	return refresh;
}

static ULONG
new_method(
			  Class * cl,
			  Object * o,
			  struct opSet *ops)
{
	struct CDPanelData *sd;
	Object *object;

	object = (Object *) DoSuperMethodA(cl, o, (Msg) ops);
	if (object)
	{
		sd = INST_DATA(cl, object);

		if (sd->gad_Graph = AllocVec( 320, MEMF_ANY ))
		{
			sd->gad_BgPen = 1;
			sd->gad_FgPen = ~0;

			tagset(cl, object, ops);
		
			if (!sd->gad_Font)
				sd->gad_Font = GfxBase->DefaultFont;

			if (sd->gad_NoBorder || 
				(sd->gad_BevelFrame = NewObject( BEVEL_GetClass(), NULL, BEVEL_Style, BVS_BUTTON, TAG_END )))
			{
				return ((ULONG)object);
			}
		}
		CoerceMethod(cl, object, OM_DISPOSE);
	}
	return (0L);
}

static ULONG
dispose_method(
				  Class * cl,
				  Object * o,
				  Msg message)
{
	struct CDPanelData *sd = INST_DATA(cl, o);
	ULONG retval;
	
	if (sd->gad_Font != GfxBase->DefaultFont)
		CloseFont( sd->gad_Font );
		
	DisposeObject(sd->gad_BevelFrame);
	
	if (sd->gad_Graph)
		FreeVec(sd->gad_Graph);
	
	retval = (ULONG) DoSuperMethodA(cl, o, message);
	return (retval);
}

static ULONG
set_method(
			  Class * cl,
			  Object * o,
			  struct opSet *ops)
{
	register struct TagItem *tstate = ops->ops_AttrList;

	if (tstate)
		tagset(cl, o, ops);

	DoSuperMethodA(cl, o, (Msg) ops);
	return 0;
}

static ULONG
get_method(
			  Class * cl,
			  Object * o,
			  struct opGet *opg)
{
	ULONG retval = TRUE;
	struct CDPanelData *sd = INST_DATA(cl, o);
	
	switch (opg->opg_AttrID)
	 {
	 case CDPANEL_Status:
	 	*opg->opg_Storage = sd->gad_Data[Status];
	 	break;
	 
	 case CDPANEL_Track:
	 	*opg->opg_Storage = sd->gad_Data[Track];
	 	break;
	 	
	 default:
		 retval = DoSuperMethodA(cl, o, (Msg) opg);
		 break;
	 }
	return (retval);
}

static ULONG
domain_method(
				 Class * cl,
				 Object * o,
				 struct gpDomain *gpd)
{
	struct CDPanelData *sd = INST_DATA(cl, o);
	ULONG t;
	
	/* GM_DOMAIN is a V42 method for asking a gadget how much space it
	 * needs/prefers.
	 * This is a fixed size gadget, so we'll always return the same thing.
	 */

	gpd->gpd_Domain.Left = gpd->gpd_Domain.Top = 0;
	gpd->gpd_Domain.Width = 26 * (sd->gad_Font->tf_XSize) + 2*(2 + 1);
	gpd->gpd_Domain.Height = 3 * (sd->gad_Font->tf_YSize + 1) + 8 + 2*(2 + 1);

	if (!sd->gad_NoBorder)
	{
		SetAttrs( sd->gad_BevelFrame, 
					IA_Width, gpd->gpd_Domain.Width, 
					IA_Height, gpd->gpd_Domain.Height,
					TAG_END );
		
		GetAttr( BEVEL_InnerWidth, sd->gad_BevelFrame, &t );
		gpd->gpd_Domain.Width += gpd->gpd_Domain.Width - t;
		GetAttr( BEVEL_InnerHeight, sd->gad_BevelFrame, &t );
		gpd->gpd_Domain.Height += gpd->gpd_Domain.Height - t;
	}
	
	return (0);
}

UBYTE IconPlaying[8] =	{ 0x40, 0x70, 0x7c, 0x7f, 0x7c, 0x70, 0x40, 0x00 };
UBYTE IconStopped[8] =	{ 0x00, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x00, 0x00 };
UBYTE IconEjected[8] =	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
UBYTE IconPaused[8]  =	{ 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00 };
UBYTE IconEmpty[8]   =	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
UBYTE IconSeeking[8] =	{ 0x38, 0x38, 0x38, 0xfe, 0x7c, 0x38, 0x10, 0x00 };

static void
DrawStatusIcon( struct RastPort *rp, UBYTE *icon, UWORD x, UWORD y )
{
	UWORD i,j;
	
	for (j = 0 ; j < 8 ; j++)
	{
		for (i = 0 ; i < 8 ; i++)
		{
			if (icon[j] & (1 << i))
				WritePixel( rp, x+7-i, y+j);
		}
	}
}

static ULONG
render_method(
				 Class * cl,
				 Object * o,
				 struct gpRender *gpr)
{
	struct CDPanelData *sd = INST_DATA(cl, o);
	struct RastPort rpfg = *gpr->gpr_RPort, rpbg = *gpr->gpr_RPort;
	UBYTE line[6];
	ULONG X, Y;
	ULONG *data = sd->gad_Data, *old = sd->gad_Old;
	ULONG baseline = sd->gad_Font->tf_Baseline;
	
	SetAPen( &rpbg, sd->gad_BgPen );
	SetDrMd( &rpbg, JAM2 );
	
	if (gpr->gpr_Redraw == GREDRAW_REDRAW)
	{
		ULONG W, H;
//		struct impDraw id;

		SetupGadgetIBox((struct Gadget *) o, &gpr->gpr_GInfo->gi_Domain, &sd->gad_Container);

		if (!sd->gad_NoBorder)
		{
#if 0
			id.MethodID = IM_DRAWFRAME;
			id.imp_RPort = &rpfg;
			id.imp_Offset.X = sd->gad_Container.Left;
			id.imp_Offset.Y = sd->gad_Container.Top;
			id.imp_State = IDS_SELECTED;
			id.imp_DrInfo = gpr->gpr_GInfo->gi_DrInfo;
			id.imp_Dimensions.Width = sd->gad_Container.Width;
			id.imp_Dimensions.Height = sd->gad_Container.Height;
			DoMethodA( sd->gad_BevelFrame, (Msg)&id );
#else
			SetAttrs( sd->gad_BevelFrame, 
					IA_Left, 0, IA_Top, 0,
					IA_Width, sd->gad_Container.Width, IA_Height, sd->gad_Container.Height,
					TAG_END );
			DrawImageState( &rpfg, (struct Image *)sd->gad_BevelFrame, sd->gad_Container.Left, sd->gad_Container.Top, IDS_SELECTED, gpr->gpr_GInfo->gi_DrInfo );
#endif
			
			GetAttr( BEVEL_InnerLeft, sd->gad_BevelFrame, &X );
			GetAttr( BEVEL_InnerTop, sd->gad_BevelFrame, &Y );
			GetAttr( BEVEL_InnerWidth, sd->gad_BevelFrame, &W );
			GetAttr( BEVEL_InnerHeight, sd->gad_BevelFrame, &H );
		}
		else
		{
			X = 0;
			Y = 0;
			W = sd->gad_Container.Width;
			H = sd->gad_Container.Height;
		}
		
		X += sd->gad_Container.Left + 1;
		Y += sd->gad_Container.Top + 1;
		W -= 2;
		H -= 2;
		RectFill( &rpbg, X, Y, X + W - 1, Y + H - 1 );

		sd->gad_GraphLen = W - 13;
					
		memset(old, ~0, DATA_MAX * sizeof(ULONG));
	}
	else
	{
		if (sd->gad_NoBorder)
		{
			X = Y = 0;
		}
		else
		{
			GetAttr( BEVEL_InnerLeft, sd->gad_BevelFrame, &X );
			GetAttr( BEVEL_InnerTop, sd->gad_BevelFrame, &Y );
		}
		
		X += sd->gad_Container.Left + 1;
		Y += sd->gad_Container.Top + 1;
	}
	
	SetAPen( &rpfg, sd->gad_FgPen );
	SetBPen( &rpfg, sd->gad_BgPen );
	SetDrMd( &rpfg, JAM2 );
	SetFont( &rpfg, sd->gad_Font );
	
	X += 2;
	Y += 2;
	
	if (data[Track] != old[Track] || data[Tracks] != old[Tracks])
	{
		/* track count */
		Sprintf( line, "%02.2ld", data[Status] == CDP_STOPPED ? data[Tracks] : data[Track] );
		Move( &rpfg, X, Y + baseline );
		RectFill( &rpbg, X, Y, X + 2 * (sd->gad_Font->tf_XSize), Y + sd->gad_Font->tf_YSize );
		Text( &rpfg, line, 2 /* constant length string */ );
	}
	
	if (data[TrackTime] != old[TrackTime])
	{
		/* playing time on this track */
		Sprintf( line, "%02.2ld:%02.2ld", data[TrackTime]/60, data[TrackTime]%60 );
		Move( &rpfg, X + 3 * (sd->gad_Font->tf_XSize), Y + baseline );
		RectFill( &rpbg, X + 3 * (sd->gad_Font->tf_XSize), Y, X + 8 * (sd->gad_Font->tf_XSize), Y + sd->gad_Font->tf_YSize );
		Text( &rpfg, line, 5 /* constant length string */ );
	}
	
	if (data[Time] != old[Time])
	{
		ULONG time = data[TrackTime];
		
		if (sd->gad_TrackTimes && data[Track])
			time = sd->gad_TrackTimes[data[Track]] - data[Time];
		
		/* remaining time on this track */
		Sprintf( line, "%02.2ld:%02.2ld", time/60, time%60 );
		Move( &rpfg, X + 9 * (sd->gad_Font->tf_XSize), Y + baseline );
		RectFill( &rpbg, X + 9 * (sd->gad_Font->tf_XSize), Y, X + 14 * (sd->gad_Font->tf_XSize), Y + sd->gad_Font->tf_YSize );
		Text( &rpfg, line, 5 /* constant length string */ );

		/* playing time from the start of the CD */
		Sprintf( line, "%02.2ld:%02.2ld", data[Time]/60, data[Time]%60 );
		Move( &rpfg, X + 15 * (sd->gad_Font->tf_XSize), Y + baseline );
		RectFill( &rpbg, X + 15 * (sd->gad_Font->tf_XSize), Y, X + 20 * (sd->gad_Font->tf_XSize), Y + sd->gad_Font->tf_YSize );
		Text( &rpfg, line, 5 /* constant length string */ );
	}
	
	if (data[TotalTime] != old[TotalTime])
	{
		/* total length of the CD */
		Sprintf( line, "%02.2ld:%02.2ld", data[TotalTime]/60, data[TotalTime]%60 );
		Move( &rpfg, X + 21 * (sd->gad_Font->tf_XSize), Y + baseline );
		RectFill( &rpbg, X + 21 * (sd->gad_Font->tf_XSize), Y, X + 26 * (sd->gad_Font->tf_XSize), Y + sd->gad_Font->tf_YSize );
		Text( &rpfg, line, 5 /* constant length string */ );
	}
	
	Y += sd->gad_Font->tf_YSize + 1;
	if (data[Artist] != old[Artist])
	{
		/* show artist name */
		ULONG len;
		if (!data[Artist])
			data[Artist] = (ULONG)"CompactPlayer";
		len = strlen((STRPTR)data[Artist]);
		Move( &rpfg, X, Y + baseline );
		RectFill( &rpbg, X, Y, X + 26 * (sd->gad_Font->tf_XSize), Y + sd->gad_Font->tf_YSize );
		Text( &rpfg, (STRPTR)data[Artist], len > 26 ? 26 : len );
	}
	
	/* title */
	Y += sd->gad_Font->tf_YSize + 1;
	if (sd->gad_TrackTitles && data[Track] && data[Track] != old[Track])
	{
		/* show track title */
		ULONG len = strlen(sd->gad_TrackTitles[data[Track]-1]);
		Move( &rpfg, X, Y + baseline );
		RectFill( &rpbg, X, Y, X + 26 * (sd->gad_Font->tf_XSize), Y + sd->gad_Font->tf_YSize );
		Text( &rpfg, sd->gad_TrackTitles[data[Track]-1], len > 26 ? 26 : len );
	}
	else
	if (data[Title] != old[Title] || data[Track] != old[Track])
	{	
		/* show disc title */
		ULONG len;
		if (!data[Title])
			data[Title] = (ULONG)"By Osma Ahvenlampi";
		len = strlen((STRPTR)data[Title]);
		Move( &rpfg, X, Y + baseline );
		RectFill( &rpbg, X, Y, X + 26 * (sd->gad_Font->tf_XSize), Y + sd->gad_Font->tf_YSize );
		Text( &rpfg, (STRPTR)data[Title], len > 26 ? 26 : len );
	}

	/* track graph */
	Y += sd->gad_Font->tf_YSize + 1;
	
	if (data[Tracks] != old[Tracks])
	{
		ULONG x = X + /*sd->gad_GraphLen*/ data[TotalTime] * sd->gad_GraphLen / (80*60);
		/* draw the disc time line */
		RectFill( &rpbg, X, Y, X + sd->gad_GraphLen, Y + 6 );
		Move( &rpfg, X, Y + 3 );
		Draw( &rpfg, X, Y + 6 );
		Move( &rpfg, X, Y + 5 );
		Draw( &rpfg, x, Y + 5 );
		Move( &rpfg, x, Y + 3 );
		Draw( &rpfg, x, Y + 6 );
	
		if (sd->gad_TrackTimes)
		{
			/* mark the track limits */
			ULONG i = 0;
			while (i < data[Tracks])
			{	
				x = X + sd->gad_TrackTimes[i++] * sd->gad_GraphLen / (80*60) /*data[TotalTime]*/;
				Move( &rpfg, x, Y + 4 );
				Draw( &rpfg, x, Y + 6 );
			}
		}
	}

	if (data[Status] != old[Status])
	{
		/* draw the icon */
		ULONG x = X + sd->gad_GraphLen + 1;
		UBYTE *i;
		RectFill( &rpbg, x, Y, x + 7, Y + 7 );
		
		switch (data[Status])
		{
		case CDP_EMPTY:
			i = IconEmpty;
			break;
		case CDP_PLAYING:
			i = IconPlaying;
			break;
		case CDP_PAUSED:
			i = IconPaused;
			break;
		case CDP_STOPPED:
			i = IconStopped;
			break;
		case CDP_SEEKING:
			i = IconSeeking;
			break;
		case CDP_EJECTED:
			i = IconEjected;
			break;
		}
		DrawStatusIcon( &rpfg, i, x, Y );
	}
	
	if (data[Time] != old[Time])
	{
		/* draw the line at current track position */
		ULONG x = data[Time] * sd->gad_GraphLen / (80*60)/*data[TotalTime]*/;
#if 1
		struct RastPort rp = rpfg;
		struct BitMap bm;
		UBYTE *b = AllocRaster(((sd->gad_GraphLen+15)>>4)<<1, 8);
		ULONG i;
		
		bm.Rows = 1;
		bm.BytesPerRow = ((sd->gad_GraphLen+15)>>4)<<1;
		bm.Depth = 8;
		
		for ( i = 0 ; i < 8 ; i++ )
			bm.Planes[i] = b + i * bm.BytesPerRow;
			
		rp.Layer = NULL;
		rp.BitMap = &bm;

		if (data[Status] == CDP_PLAYING || data[Status] == CDP_SEEKING)
		{
			if (data[Track] != old[Track] || data[Time] < old[Time])
				memset(sd->gad_Graph + x, sd->gad_BgPen, sd->gad_TrackTimes[data[Track]] * sd->gad_GraphLen / (80*60) - x);

			if (!(x % 2))
				sd->gad_Graph[x] = sd->gad_FgPen;
		}
			
		WritePixelLine8(&rpfg, X, Y+2, sd->gad_GraphLen, sd->gad_Graph, &rp);
		
		FreeRaster(b, ((sd->gad_GraphLen+15)>>4)<<1, 8);
#else		
		if ( x % 2 )
		{
			WritePixel( &rpfg, X + x, Y + 2 );
		}
		if (data[Track] != old[Track] || data[Time] < old[Time])
			RectFill( &rpbg, X + x + 1, Y + 2, X + sd->gad_TrackTimes[data[Track]] * sd->gad_GraphLen / (80*60), Y + 2);
#endif
	}
	
	memcpy(old, data, DATA_MAX * sizeof(ULONG));
	
	return (0);
}

static ULONG __saveds __asm
CDPanelDispatch(
						register __a0 Class * cl,
						register __a2 Object * o,
						register __a1 Msg message)
{
	ULONG retval;
	
	switch (message->MethodID)
	 {
	 case GM_DOMAIN:
		 retval = domain_method(cl, o, (struct gpDomain *) message);
		 break;

	 case GM_RENDER:
		 retval = render_method(cl, o, (struct gpRender *) message);
		 break;

	 case GM_HITTEST:
		 retval = 0;
		 break;

	 case GM_HANDLEINPUT:
	 case GM_GOINACTIVE:
	 case GM_GOACTIVE:
		 retval = GMR_NOREUSE;
		 break;

	 case OM_SET:
	 case OM_UPDATE:
		 retval = set_method(cl, o, (struct opSet *) message);
		 break;

	 case OM_NEW:
		 retval = new_method(cl, o, (struct opSet *) message);
		 break;

	 case OM_DISPOSE:
		 retval = dispose_method(cl, o, message);
		 break;

	 case OM_GET:
		 retval = get_method(cl, o, (struct opGet *) message);
		 break;
		 
	 default:
		 retval = DoSuperMethodA(cl, o, message);
		 break;
	 }

	return (retval);
}

/* function to create/initialize our CLASS */
Class *
CreateCDPanelClass(void)
{
	Class *cl;

	if (cl = MakeClass( NULL, "gadgetclass", NULL, sizeof(struct CDPanelData), 0))
	{
		cl->cl_Dispatcher.h_Entry = (unsigned long (*)())CDPanelDispatch;
		cl->cl_Dispatcher.h_SubEntry = NULL;

		BevelBase = (struct ClassLibrary *) OpenLibrary("images/bevel.image", 0L);
		if (BevelBase)
		{
			return (cl);
		}
		FreeClass( cl );
	}
	return (0);
}

void
DestroyCDPanelClass(struct Class * cl)
{
	if (BevelBase)
	{
		CloseLibrary((struct Library *)BevelBase);
		BevelBase = NULL;
	}
	if (cl)
	{
		FreeClass((struct IClass *)cl);
	}
}

