/* dial custom gadget implementation	*/

/*
 * Limitations:
 *	no RELWIDTH/RELHEIGHT (uses Width/Height)
 *	no ActivateGadget() or manual toggling of SELECTED 
 *	Image rendering only, no SelectImage.
 */

#include "math.h"
#include "sysall.h"

struct Gadget	*CreateGadget();

#define D( x )	;	/* debugging: on 'x', off ';'	*/

#define DGAVECS	(6)	/* area info vectors for needle	*/

/*** black box dial gadget data ***/
struct DialInfo {
    int		coordX;		/* ray vector of needle			*/
    int		coordY;
    int		baseX;		/* vector for base of needle		*/
    int		baseY;
    struct AreaInfo	ainfo;
    struct TmpRas	tmpras;
    UBYTE	abuffer[ DGAVECS * 5 ];
};
#define DI( g ) 	( (struct DialInfo *) (g)->SpecialInfo )

#define BASEFACTOR	(10)	/* relative width of needle		*/
#define NEEDLEPEN	(1)	/* pen color of needle			*/
#define CENTER(dim)	(((dim) - 1)/2)

/*** hook implementations ***/
/* forward in this file	*/
ULONG	dial_Dispatch();	/* vectors off to the others	*/
ULONG	dial_hittest();
ULONG	dial_render();
/* ULONG	dial_goactive(); **** use handleinput ***/
ULONG	dial_goinactive();
ULONG	dial_handleinput();


/* interface to get my dispatcher called by Intuition	*/
ULONG	msgface();
struct IHook dialhook =  { msgface, dial_Dispatch, 0 };

struct Gadget	*
CreateDialGadget( id, im )
struct Image	*im;
{
    struct Gadget	*g;
    struct DialInfo	*di;
    UBYTE		*tmpraster;
    int			rassize;

    /**** get the library open by manx	****/
    float	a, b;
    a = b * 2.0;

    if ( g = CreateGadget( im->Width, im->Height, id, NULL,
		sizeof ( struct DialInfo ) ) )
    {
	di = DI( g );

	g->GadgetType = CUSTOMGADGET;
	g->Flags |= GADGIMAGE;
	g->MutualExclude = (ULONG) &dialhook;
	g->GadgetRender = (APTR) im;

	SetDialCoords( g, 0, 1 );	/* initially "north"	*/

	/* need to set up a temp raster for a dial gadget
	 * as well as AreaInfo.
	 * could probably arrange to share tmp rasters between
	 * dial gadgets no problem, via a SetDialGadgetTmpRaster()
	 * function call
	 */
	InitArea( &di->ainfo, di->abuffer, (long) DGAVECS );

	rassize = RASSIZE( (long) im->Width, (long) im->Height );
	if (  tmpraster = AllocMem( (long) rassize, (LONG) MEMF_CHIP ) )
	{
	    InitTmpRas( &di->tmpras, tmpraster, (long) rassize );
	}
	else	/* abort	*/
	{
	    D( printf("CDG: couldn't get tmpraster size: %d\n", rassize ));
	    FreeGadgets( g );
	    g = NULL;
	}
    }

    return ( g );
}

DeleteDialGadget( g )
struct Gadget	*g;
{
    struct TmpRas	*tmpras = &DI( g )->tmpras;

    /* free off tmp raster	*/
    FreeMem( tmpras->RasPtr, tmpras->Size );

    /* delete gadget, special info, ... */
    g->NextGadget = NULL;
    FreeGadgets( g );
}

/* returns TRUE if normalized coord values have changed	*/
SetDialCoords( g, x, y )
struct Gadget	*g;
{
    WORD	oldx, oldy;
    struct DialInfo	*di = DI( g );

    if ( !( x || y ) )
    {
    	return ( SetDialCoords( g, 0, 1 ) );
    }

    GetDialCoords( g, &oldx, &oldy );

    DI(g)->coordX = x;
    DI(g)->coordY = y;
    normalizeDialCoords( g );

    /* check for changes	*/
    GetDialCoords( g, &x, &y );

    return ( x != oldx || y != oldy );
}

GetDialCoords( g, xp, yp )
struct Gadget	*g;
WORD	*xp;
WORD	*yp;
{
    *xp = DI(g)->coordX;
    *yp = DI(g)->coordY;
}

static
drawNeedle( rp, g, left, top )
struct RastPort	*rp;
struct Gadget *g;
{
    struct DialInfo	*di = DI( g );
    long	centerX;
    long	centerY;
    struct TmpRas	*savetr;
    struct AreaInfo	*saveai;

    /**** set up my tmpras/ainfo in Intuition's rastport	****/
    savetr = rp->TmpRas;
    saveai = rp->AreaInfo;

    rp->TmpRas = &di->tmpras;
    rp->AreaInfo = &di->ainfo;

    centerX = CENTER( g->Width );
    centerY = CENTER( g->Height );

    SetAPen( rp, (long) NEEDLEPEN );

    AreaMove( rp, (long) left + centerX + di->baseX,
	    	 (long) top + centerY + di->baseY );

    AreaDraw( rp, (long) left + centerX - di->baseX,
	    	 (long) top + centerY - di->baseY );

    AreaDraw( rp, (long) left + centerX + di->coordX,
	    	 (long) top + centerY + di->coordY );

    AreaEnd( rp );

    /*** restore Intuition's tmpras and ainfo	***/
    rp->TmpRas = savetr;
    rp->AreaInfo = saveai;
}

/*** internal number crunching ***/

struct FPoint	{
	float	fX;
	float	fY;
};

/*
 * converts values of coordX coordY to ray equivalent
 * coordinates on ellipse boundary of dial.
 * Never call this if the coords are both zero.
 */
static
normalizeDialCoords( g )
struct Gadget	*g;
{
    struct DialInfo	*di = DI( g );
    struct FPoint	edge;
    struct FPoint	base;
    int			halfx = CENTER( g->Width );
    int			halfy = CENTER( g->Height );
    float		norm;
    struct HitPoint	ray;

    ray.X = di->coordX;
    ray.Y = di->coordY;

    norm =	(float) halfy * halfy * ray.X * (float) ray.X +
    		(float) halfx * halfx * ray.Y * (float) ray.Y;

    norm = sqrt( (double) norm );

    /* I forgot what clever math leads to this	*/
    edge.fX = (float) halfy * ray.X / norm;
    edge.fY = (float) halfx * ray.Y / norm;

    base.fX = edge.fY / BASEFACTOR;
    base.fY = - edge.fX / BASEFACTOR;

    di->coordX = halfx * edge.fX;
    di->coordY = halfy * edge.fY;

    di->baseX = halfx * base.fX;
    di->baseY = halfy * base.fY;
}

/**** the hook functions	****/
/* Note: these functions run as the input.device task.
 * This means, among other things, that you can't do printf's
 * to your own process's output window.
 *
 * Also, it is not safe to call Intuition or Graphics, except as
 * documented for Intuition hook functions.
 */

ULONG
dial_Dispatch( gi, msg )
struct GadgetInfo	*gi;
ULONG			*msg;	/* only interested in HookID	*/
{
    ULONG retval = 0;


    D( kprintf("\ndial_Dispatch. gi %lx, msg: %lx id %ld\n", gi, msg, *msg ) );

    switch ( (WORD) *msg )
    {
    case GH_HITTEST:
        retval = dial_hittest( gi, msg );
	break;
    case GH_RENDER:
        retval = dial_render( gi, msg );
	break;
    case GH_GOINACTIVE:
        retval = dial_goinactive( gi, msg );
	break;
    case GH_GOACTIVE:
    case GH_HANDLEINPUT:
        retval = dial_handleinput( gi, msg );
	break;
    }
    return ( retval );
}


/*
 * for now, if it's in the select box, we'll call it
 * a hit.  (Might mask test or restrict to ellipse later.)
 * Note that this function is only called if select box
 * test has passed in Intuition.
 */
static ULONG
dial_hittest( gi, msg )
struct GadgetInfo	*gi;
struct CGPHit		*msg;
{
    D( kprintf("dial_hittest\n") );
    return ( GHR_GADGETHIT );
}

/*
 * this implementation has no highlighting visuals.
 * so, if redraw is 0, I don't draw at all.
 * Note that I use this function to update the visuals
 * in dial_handleinput, in which case a NULL hook is passed.
 * This would change if you wanted some more subtle incremental
 * changing while the mouse was moved.
 */
static ULONG
dial_render( gi, msg )
struct GadgetInfo	*gi;
struct CGPRender	*msg;
{
    ULONG dial_realrender();

    D( kprintf("dial_realrender: gi %lx, rp %lx, redraw %ld\n",
    	gi, msg->cgp_RPort, msg->cgp_Redraw ) );
    return ( dial_realrender( gi, msg->cgp_RPort, msg->cgp_Redraw ) );
}

static ULONG
dial_realrender( gi, rp, redraw )
struct GadgetInfo	*gi;
struct RastPort	*rp;
LONG		redraw;
{
    if ( redraw )
    {
    	DrawImage( rp, gi->gi_Gadget->GadgetRender,
		(long) gi->gi_Box.Left, (long) gi->gi_Box.Top );
	drawNeedle( rp, gi->gi_Gadget, gi->gi_Box.Left, gi->gi_Box.Top );
    }
}

/*
 * since this gadget doesn't have any special allocations
 * or processing when it is made active, this function
 * is a no-op
 */
static ULONG
dial_goinactive( gi, msg )
struct GadgetInfo	*gi;
CPTR	msg;
{
	D( kprintf("dial_goinactive\n") );
	return ( 0 );	
}

static ULONG
dial_handleinput( gi, msg )
struct GadgetInfo	*gi;
struct CGPHandleInput	*msg;
{
    struct RastPort	*ObtainGIRPort();
    struct RastPort	*rp;
    struct HitPoint	mouse;
    ULONG		retval = 0;
    struct Gadget	*g = gi->gi_Gadget;
    struct InputEvent	*ie = msg->cgp_IEvent;

    D( kprintf( "dial_handleinput. gi %lx ie %lx\n", gi, ie ) );

    if ( ie->ie_Class == IECLASS_RAWMOUSE )
    {
	/* in any case, update coords and visuals	*/
    	GadgetMouse( gi, &mouse );

	/* only redraw if it has moved	*/
	if ( SetDialCoords( gi->gi_Gadget,
		mouse.X - CENTER( g->Width ), mouse.Y - CENTER( g->Height ) ) )
	{
	    rp = ObtainGIRPort( gi );

	    /* use normal rendering function, passing it a NULL hook	*/
	    dial_realrender( gi, rp, 1L );

	    FreeGIRPort( rp );
	}

	/* determine if we are done	*/

	/* menu button: abort, input reused	*/
	if ( ie->ie_Code == IECODE_RBUTTON )
	{
	    *msg->cgp_Termination = 1;	/* aborted by menu button	*/
	    retval = GHR_REUSE | GHR_VERIFY;
	}
	/* select up: user is done, don't reuse input event	*/
	else  if ( ie->ie_Code == (IECODE_LBUTTON | IECODE_UP_PREFIX ) )
	{
	    *msg->cgp_Termination = 0;	/* normal termination */
	    retval = GHR_NOREUSE | GHR_VERIFY;
	}
    }

    D( kprintf( "d_hi returning: %lx\n", retval ) );
    return ( retval );
}

