/* dial custom gadget implementation	*/

/* MAKE SURE YOU COMPILE THIS FILE WITH STACK CHECKING TURNED OFF.	*/

/*
Copyright (c) 1989 Commodore-Amiga, Inc.

Executables based on this information may be used in software
for Commodore Amiga computers. All other rights reserved.
This information is provided "as is"; no warranties are made.
All use is at your own risk, and no liability or responsibility
is assumed.
*/

/*
 * 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 {
    WORD	coordX;		/* ray vector of needle			*/
    WORD	coordY;
    WORD	baseX;		/* vector for base of needle		*/
    WORD	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_render();
ULONG	dial_handleinput();

/* see StrDemo/strhooks.c for more info on this
 * particular implementation of a Hook callback
 * asm-to-C interface routine.
 */
ULONG __saveds __asm
hookEntry( register __a0 struct Hook *hookptr,
	register __a2 void	*object,
	register __a1 void	*message )
{
	D( kprintf("hookEntry\n"));
	return ( (*hookptr->h_SubEntry)( hookptr, object, message ) );
}

/* interface to get my dispatcher called by Intuition.
 * this hook can be shared between several dial gadgets
 */
struct Hook dialhook =  { {0,0}, hookEntry, dial_Dispatch, 0 };

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

    D( printf( "CDG: id: %lx, im %lx\n", id, im ) );

    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, DGAVECS );

	rassize = RASSIZE( (long) im->Width, (long) im->Height );
	if (  tmpraster = (UBYTE *) AllocMem( rassize, MEMF_CHIP ) )
	{
	    InitTmpRas( &di->tmpras, tmpraster, 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, NEEDLEPEN );

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

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

    AreaDraw( rp, left + centerX + di->coordX,
	    	 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;
    WORD		halfx = CENTER( g->Width );
    WORD		halfy = CENTER( g->Height );
    float		norm;
    Point		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( hook, g, msg )
struct Hook	*hook;	/* I don't have any use for this */
struct Gadget	*g;
ULONG		*msg;	/* only interested in HookID	*/
{
    ULONG retval = 0;

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

    switch ( (WORD) *msg )
    {
    case GM_HITTEST:
	/*
	 * 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.
	 */
	retval = GMR_GADGETHIT;
	break;

    case GM_RENDER:
        retval = dial_render( g, msg );
	break;

    case GM_GOINACTIVE:
	/*
	 * since this gadget doesn't have any special allocations
	 * or processing when it is made active, this function
	 * is a no-op
	 */
        retval = 0;
	break;

    case GM_GOACTIVE:		/* first input event		*/
    case GM_HANDLEINPUT:	/* subsequent input events	*/
        retval = dial_handleinput( g, msg );
	break;
    }
    return ( retval );
}

/*
 * 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( g, msg )
struct Gadget	*g;
struct gpRender	*msg;
{
    ULONG dial_realrender();

    return( dial_realrender( g, msg->gpr_GInfo,
	    	msg->gpr_RPort, msg->gpr_Redraw ) );
}

static ULONG
dial_realrender( g, gi, rp, redraw )
struct Gadget		*g;
struct GadgetInfo	*gi;
struct RastPort		*rp;
LONG			redraw;
{
    /* this code uses the gadget select box as is, therefore,
     * this gadget doesn't support relative positioning or dimensions
     */

    if ( redraw )
    {
    	DrawImage( rp, g->GadgetRender, g->LeftEdge, g->TopEdge );
	drawNeedle( rp, g, g->LeftEdge, g->TopEdge );
    }

    return ( 0 );
}


static ULONG
dial_handleinput( g, msg )
struct Gadget	*g;
struct gpInput	*msg;
{
    struct GadgetInfo	*gi = msg->gpi_GInfo;
    struct RastPort	*ObtainGIRPort();
    struct RastPort	*rp;
    Point		mouse;
    ULONG		retval = 0;
    struct InputEvent	*ie = msg->gpi_IEvent;

    if ( ie->ie_Class == IECLASS_RAWMOUSE )
    {
	mouse.x = msg->gpi_Mouse.X;
	mouse.y = msg->gpi_Mouse.Y;

	/* only redraw if it has moved	*/
	if ( SetDialCoords( g,
		mouse.x - CENTER( g->Width ), mouse.y - CENTER( g->Height ) ) )
	{
	    /* you have to ask permission to use the right rastport
	     * if it isn't provided in the message packet (gpRender
	     * is the only case of that).
	     */
	    rp = ObtainGIRPort( gi );

	    /* render it with new coord values	*/
	    dial_realrender( g, gi, rp, 1L );

	    ReleaseGIRPort( rp );
	}

	/* determine if we are done	*/

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

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