/* mymodelclass.c -- :ts=8
 * Example of a simple subclass of "modelclass".
 * It maintains an integer "current value", which
 * it keeps between 0 and some specified maximum.
 */

/*
Copyright (c) 1989, 1990 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.
*/

#include "sysall.h"
#include <clib/alib_protos.h>
#include <intuition/classes.h>
#include "mymodel.h"		/* attributes are defined there	*/

#ifdef printf
#undef printf
#endif

#define printf kprintf


#define D(x)	;

/* private class	*/
#define MYCLASSID	(NULL)
extern struct Library	*IntuitionBase;
#define SUPERCLASSID	MODELCLASS

BOOL setMyModAttrs( Class *cl, Object *o, Msg msg );
BOOL getMyModAttr( Class *cl, Object *o, struct opGet *msg );
BOOL notifyAttrChanges( Object *o, void *ginfo, ULONG flags, ULONG tag1,... );


struct MyModData	{
    ULONG	mmd_CurrentValue;
    ULONG	mmd_Range;		/* current value <= range-1	*/
};

Class	*initMyModClass(void)
{
    ULONG __saveds	dispatchMyMod();
    ULONG	hookEntry();
    Class	*cl;
    Class	*MakeClass();

    if ( cl =  MakeClass( MYCLASSID, 
		SUPERCLASSID, NULL,		/* superclass is public      */
 		sizeof ( struct MyModData ),
		0 ))
    {
	/* initialize the cl_Dispatcher Hook	*/
	cl->cl_Dispatcher.h_Entry = hookEntry;
	cl->cl_Dispatcher.h_SubEntry = dispatchMyMod;
	cl->cl_Dispatcher.h_Data = (VOID *) 0xFACE;	/* unused */
    }
    return ( cl );
}

BOOL freeMyModClass( Class *cl )
{
    return ( FreeClass( cl )  );
}

__saveds ULONG dispatchMyMod( Class *cl, Object *o, Msg msg )
{
    Object	*newobj;
    ULONG	oldval;
    ULONG	oldrng;
    ULONG	notify_msg_flags = 0;
    ULONG	interim_flag;

    struct MyModData	*mmd;

    D( printf("mymodel dispatcher, method ID %lx\n", msg->MethodID ) );

    mmd = INST_DATA( cl, o );

    switch ( msg->MethodID )
    {
    /* use superclass defaults for everything else */
    case OM_NEW:
	D( printf("mymodel: OM_NEW\n") );
	if( newobj = (Object *) DSM( cl, o, msg ) )
	{
	    D( printf("new model object at %lx\n", newobj ) );
	    /* initialize instance data (they start life as 0)	*/
	    setMyModAttrs( cl, newobj, msg );
	}
	return ( (ULONG) newobj );

    case OM_GET:
	return ( (ULONG) getMyModAttr( cl, o, (struct opGet *)msg ) );

    case OM_SET:
    case OM_UPDATE:
	D( printf("mymod update\n" ) );
	if ( ! DoSuperMethod( cl, o, ICM_CHECKLOOP ) ) 
	{
	    /* let the superclass see whatever it wants from OM_SET,
	     * such as ICA_TARGET, ICA_MAP, and so on.  For OM_NOTIFY,
	     * however, we control all traffic and issue notification
	     * specifically for the attributes we're interested in.
	     */
	    if ( msg->MethodID == OM_SET )
	    {
		D( printf("mymod update is actually OM_SET\n"));
		DSM( cl, o, msg );
	    }
	    else
	    {
		/* these flags aren't present in the message of OM_SET	*/
		notify_msg_flags =  ((struct opUpdate *)msg)->opu_Flags;
	    }

	    /*
	     * I'll be wanting to know this is an "interim" message
	     * or a final report (which I always want to send, even
	     * if the value of mmd_CurrentValue hasn't changed).
	     */
	    interim_flag =  notify_msg_flags & OPUF_INTERIM;

	    /* Now set possibly new value of mmd_CurrentVal, and
	     * maybe a range change.
	     * Only send a notification message along for values of
	     * interest that have changed.
	     */

	    /* save 'em	*/
	    oldval = mmd->mmd_CurrentValue;
	    oldrng = mmd->mmd_Range;

	    /* change 'em, only if changed (or if
	     * a "non-interim" message.
	     */
	    if ( setMyModAttrs( cl, o, msg ) || ! interim_flag )
	    {
	    	Tag	rangetag;
	    	Tag	currvaltag;

/* if condition is false, replace tag with TAG_IGNORE	*/
#define XTAG( expr, tagid ) ((expr)? (tagid): TAG_IGNORE)

		rangetag = (oldrng!=mmd->mmd_Range)? MYMODA_RANGE: TAG_IGNORE;
		if ( ! interim_flag || (oldval != mmd->mmd_CurrentValue) )
		{
		    currvaltag = MYMODA_CURRVAL;
		}
		else
		{
		    currvaltag = TAG_IGNORE;
		}


		D( printf("mymod: sending notification\n") );

		/* Pass along GInfo, if any, so gadgets can redraw
		 * themselves.  Pass along opu_Flags, so that the
		 * application will know the difference between
		 * and interim message and a final message
		 */
		notifyAttrChanges( o, ((struct opSet *)msg)->ops_GInfo,
			interim_flag,
			rangetag,	mmd->mmd_Range,
			currvaltag,	mmd->mmd_CurrentValue,
			TAG_END );
	    }
	    D( else printf("setMyModAttrs returnes 'nochange'\n"));
	}
	D( else printf("Loop Check violation!\n"));
	break;

    case OM_NOTIFY:
    	D( printf("mymod: forwarding OM_NOTIFY to superclass modelclass\n"));
    case OM_DISPOSE:
    default:
	D( printf("let superclass handle it\n"));
	return ( (ULONG) DSM( cl, o, msg ) );
    }
    return ( 1 );
}

BOOL setMyModAttrs( Class *cl, Object *o, Msg msg )
{
    struct TagItem	*tags = ((struct opSet *)msg)->ops_AttrList;
    struct MyModData	*mmd;
    int			changes = FALSE;
    LONG		newval;
    ULONG		newrng;

    mmd = INST_DATA( cl, o );

    D( printf("setMyModAttrs, object %lx\n", o ) );
    D( dumpTagList( "mymod attr tags", tags ));

    newrng =  GetTagData( MYMODA_RANGE, mmd->mmd_Range, tags );

    if ( mmd->mmd_Range != newrng )
    {
	D( printf( "mymod: range has changed value to %ld\n",
		mmd->mmd_Range ));
	mmd->mmd_Range =  newrng;
	changes = TRUE;
    }

    /* validity check	*/
    if ( mmd->mmd_Range == 0 )
    {
	mmd->mmd_Range = 1;
	changes = TRUE;
    }

    D( printf("range is %ld\n", mmd->mmd_Range ) );

    /* start with original value	*/
    newval =  mmd->mmd_CurrentValue;

    D( printf("original currval %ld\n", mmd->mmd_CurrentValue ) );

    /* increment/decrement in response to strobes	*/
    if ( GetTagData( MYMODA_INCRSTROBE, 0, tags ) > 0 )
    {
	newval++;
	D( printf("strobe increment newval to %ld\n", newval ) );
    }
    if ( GetTagData( MYMODA_DECRSTROBE, FALSE, tags ) > 0 )
    {
	if ( newval > 0 ) newval--;
	D( printf("strobe decrement newval to %ld\n", newval ) );
    }

    /* look at "absolute" setting last	*/
    newval = GetTagData( MYMODA_CURRVAL, newval, tags );

    D( printf("unconstrained newval %ld\n", newval ) );

    /* limit mmd_CurrentValue to mmd_Range-1	*/
    if ( newval < 0 ) newval = 0;
    if ( newval > (mmd->mmd_Range-1) ) newval = mmd->mmd_Range - 1;

    D( printf( "final: new current value %ld\n", newval ));

    if ( mmd->mmd_CurrentValue != newval )
    {
    	mmd->mmd_CurrentValue = newval;
	D( printf( "mymod: value has changed to %ld\n",
		mmd->mmd_CurrentValue ));
	changes = TRUE;
    }

    return ( changes );
}

BOOL getMyModAttr( Class *cl, Object *o, struct opGet *msg )
{
    struct MyModData	*mmd;

    mmd = INST_DATA( cl, o );

    switch ( msg->opg_AttrID )
    {
    case MYMODA_CURRVAL:
	*msg->opg_Storage = mmd->mmd_CurrentValue;
	break;
    case MYMODA_RANGE:
	*msg->opg_Storage = mmd->mmd_Range;
	break;
    default:
	/* I don't recognize this one, let the superclass try	*/
	return ( DSM( cl, o, msg ) );
    }
    return ( TRUE );
}

/*
 * a convenient way to construct and send an OM_NOTIFY message
 */
BOOL notifyAttrChanges( Object *o, void *ginfo, ULONG flags, ULONG tag1,... )
{
    return ( DoMethod( o, OM_NOTIFY, &tag1, ginfo, flags ) );
}
