Making your own class

When making your own classes I advise you to use the Skeleton class and then proceed from there since it already has a lot of the dirty work finished for you so you can get right to writing the guts of your class.

A Button Class

I figure the best way to go about this is to just take you through the code of a button class... Any bugs found while writing this are left as an exercise for the reader :)

Header File

#ifndef BBBUTTONCLASS_H
#define BBBUTTONCLASS_H

#define BGA_Dummy (TAG_USER + 0x60000)
#define BGA_Push (BGA_Dummy + 1)
#define BGA_Image (BGA_Dummy + 2)

//Convenient macro for creating buttons
#define ButtonObject NewObject( NULL, "bbbuttongadget"

#endif

Casting Macros

I like to use casting macros a lot so here are some:
#define GA(o) ((struct Gadget *)o)
#define IA(o) ((struct Image *)o)
#define IM(o) ((struct Image *)o)
#define SET(o) ((struct opSet *)o)
#define GET(o) ((struct opGet *)o)
#define GPR(o) ((struct gpRender *)o)
#define GPI(o) ((struct gpInput *)o)
#define GPL(o) ((struct gpLayout *)o)
They just provide a simple and quick way to cast BOOPSI style messages from Msg to the appropriate structure for that message

Flags

Just about every class needs some flags associated with it to encode options or states.
#define BB_TEXT 0 //is the label text?
#define BB_PUSH 1 //should the button hold its state when (de)selected

#define BF_TEXT (1L << BB_TEXT)
#define BF_PUSH (1L << BB_PUSH)

Protos

Instead of just packing every bit of code into dispatcher its a good idea to break it up into separate functions.
//always good for debugging, since you can't use printf() from a library or even a class i think...
extern int kprintf( const char *str, ... );

//the dispatcher itself
ULONG
ASM dispatchClass( REG(a0) Class * cl, REG(a2) Object * o, REG(a1) Msg msg );

//Used to set our attributes
ULONG
ASM setClassAttrs( REG(a0) Class * cl, REG(a2) Object * o, REG(a1) struct opSet * msg );

//Used to get some attribute
ULONG
ASM getClassAttr( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opGet * msg);

//a custom TextLength() that doesn't need a RastPort
LONG myTextLength( struct TextFont *font, char *str, int len );

//The ubiquitous Notify() function found in all my gadget classes
//It takes care of sending the OM_UPDATE messages to the target object
void ASM Notify( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opSet *msg, REG(d1) ULONG flags, REG(d2) sel, REG(a3) struct GadgetInfo *ginfo );

Globals

Class *cl = 0;  // our class structure pointer
struct Library *ClasservBase;  //pointer to the classerv.library base, see the my classes page

struct DrawInfo defdri;  //a default drawinfo in case one isn't passed in
//we use it to find a height and width of buttons with text labels so we really need it

WORD defpens[NUMDRIPENS];  //default pen array for the drawinfo

//in case we're disabled we should ghost
USHORT ghostdata[] =
{
	0x2222, 0x8888
};

//our instance data, try to make it small
struct localObjData {
	struct DrawInfo *dri;
	BYTE flags;
};

//ahhh, easy open and closing of libs
struct OpenLibTemplate olt[] =
{
	{"intuition.library",36,&IntuitionBase},
	{"graphics.library",0,&GfxBase},
	{"utility.library",0,&UtilityBase},
	{"classerv.library",0,&ClasservBase},
	{0}
};

//same for classes although we don't need any here
struct OpenClassTemplate oct[] =
{
	{0}
};

Names

We need to pick a name for our gadget and for our superclass. As naming conventions go there really isn't a standard yet. Once the classerv.library is brought to its full potential there should be one.
#define MYCLASSID "bbbuttongadget"
#define SUPERCLASSID "gadgetclass"

OpenLibrary/CloseLibrary

I do all of my classes so that they are in libraries, you don't have to though, you can make private classes and public classes in your own code. I just like being able to have them shareable and easily replaced.
int ASM SAVEDS __UserLibInit( REG(a6) struct MyLibrary *libbase )
{
	//Open libraries and classes
	if( OpenLibraries( olt ) )
	{
		if( OpenClasses( oct ) )
		{
			//setup our default drawinfo
			defpens[BACKGROUNDPEN] = 0;
			defpens[SHADOWPEN] = 1;
			defpens[SHINEPEN] = 2;
			defpens[TEXTPEN] = 1;
			defdri.dri_Pens = defpens;
			defdri.dri_NumPens = NUMDRIPENS;
			defdri.dri_Font = GfxBase->DefaultFont;
			//setup our class
			if( cl = MakeClass( MYCLASSID,
 	                     SUPERCLASSID, NULL,
   	                   sizeof(struct localObjData), 0))
			{
				/* Fill in the callback hook */
				cl->cl_Dispatcher.h_Entry = (ULONG (*) ())dispatchClass;
				/* Keep track of the libbase here since we'll need it later */
				cl->cl_UserData = (ULONG)libbase;
				/* Make the class public */
				AddClass( cl );
				return( FALSE );
			}
			/* something is hosed, close the classes and libs */
		}
		CloseClasses( oct );
	}
	CloseLibraries( olt );
	return( TRUE );
}

void ASM SAVEDS __UserLibCleanup( REG(a6) struct MyLibrary *libbase )
{
	if( cl )
	{
		/* Remove and free our class structure */
		RemoveClass( cl );
		FreeClass(cl);
	}
	/* Close libs and classes, see how easy it is :) */
	CloseClasses( oct );
	CloseLibraries( olt );
}

Dispatcher... Ugh

Here comes the hard part... Notice no __saveds, it can't be used here...
ULONG ASM dispatchClass( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) Msg msg)
{
	ULONG retval = FALSE;
	Object *newobj;
	struct localObjData *lod;
C can be a pain when coding shared libraries. Just because this function is in a shared library doesn't mean that a6 will be set to our library base so we must first put our lib base, which we saved in cl_UserData, into a6 before we can get a4 and access to all our global data (library bases, etc...).
	putreg( REG_A6, cl->cl_UserData );
	geta4();

	switch (msg->MethodID)
	{
		case OM_NEW:       /* First, pass up to superclass */
#ifdef DEBUG
			kprintf( "class.class/OM_NEW:\n" );
#endif
			if(newobj = (Object *)DSM(cl, o, msg))
			{
First you pass the message up using DoSuperMethodA(), DSM() for short, and this will eventually hit rootclass creating our object and then it will come back down to us after our parents have initialized their instance data to the defaults. Then below we get our instance data pointer and set any defaults. ALWAYS make sure that your instance pointer is set and DO NOT ever copy and paste the instance macro from the OM_NEW method since it uses lod = INST_DATA( cl, newobj ) where every other method should use lod = INST_DATA(cl, o), notice it should be o not newobj. NB: You should always pass the message up to your parent class, in the future some people might want to implement error handling so that any objects passed in the taglists will be disposed of if the OM_NEW fails.
				/* Initial local instance data */
				lod = INST_DATA( cl, newobj );
				lod->dri = &defdri;
				//Use set function to interpret the tags passed in
				setClassAttrs( cl, newobj, (struct opSet *)msg );
				retval = (ULONG)newobj;
			}
			break;
		case OM_SET:
#ifdef DEBUG
			kprintf( "class.class/OM_SET:\n" );
#endif
			retval = DSM( cl, o, msg );
			retval += setClassAttrs( cl, o, SET(msg) );
			break;
		case OM_GET:
#ifdef DEBUG
			kprintf( "class.class/OM_GET:\n" );
#endif
			retval = getClassAttr( cl, o, GET(msg) );
			break;
Here comes GM_RENDER, it does all of the drawing for the gadget whenever intuition asks it too. Since this is the first function pertaining to gadget we'll discuss the GadgetInfo structure now.
struct GadgetInfo
{
	struct Screen *gi_Screen;
	struct Window *gi_Window;
	struct Requester *gi_Requester;
	
	struct RastPort *gi_RastPort;
	struct Layer *gi_Layer;
	
	struct IBox gi_Domain;
	
	struct {
		UBYTE DetailPen;
		UBYTE BlockPen;
	} gi_Pens;
	
	struct DrawInfo *gi_DrInfo;
	
	ULONG gi_Reserved[4];
};
This structure gives you a bunch of information about where your gadget is in the system as well as any information needed for drawing. It is available in all gadget method messages and the OM_SET message defined by the rootclass. Be warned though, all of the gadget methods have the pointer just after the MethodID, but the OM_SET message has it in a different place, this has caused me problems before.
		case GM_RENDER:
			{
				struct RastPort *rp = GPR(msg)->gpr_RPort;
				struct DrawInfo *dri;
				//Support for the GREL_ flags, these should be made into macros someday.
				WORD left = (GA(o)->Flags & GFLG_RELRIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->LeftEdge - 1 : GA(o)->LeftEdge;
				WORD top = (GA(o)->Flags & GFLG_RELBOTTOM) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->TopEdge - 1 : GA(o)->TopEdge;
				WORD width = (GA(o)->Flags & GFLG_RELWIDTH) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->Width : GA(o)->Width;
				WORD height = (GA(o)->Flags & GFLG_RELHEIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->Height : GA(o)->Height;

				lod = INST_DATA( cl, o );

				dri = lod->dri;  //cache a pointer to the drawinfo
				//here we check if there is a border image (most likely a frameiclass object) which we should draw else we just setup a background color
				if( GA(o)->GadgetRender )
				{
					DrawImageState( rp, GA(o)->GadgetRender, left, top, (GA(o)->Flags & GFLG_SELECTED) ? IDS_SELECTED : IDS_NORMAL, GPR(msg)->gpr_GInfo->gi_DrInfo );
					SetDrMd( rp, JAM1 );
				}
				else
				{
					SetAPen( rp, (GA(o)->Flags & GFLG_SELECTED) ? dri->dri_Pens[FILLPEN] : dri->dri_Pens[BACKGROUNDPEN] );
					RectFill( rp, left, top, left + width - 1, top + height - 1 );
				}
				if( GA(o)->GadgetText )
				{
					WORD lwidth, lheight;
					WORD xoffset, yoffset;

					//check if we're drawing some text or an image, and figure out the width and height of which one
					if( (lod->flags & BF_TEXT) )
					{
						lwidth = myTextLength( dri->dri_Font, (char *)GA(o)->GadgetText, strlen( (char *)GA(o)->GadgetText ) );
						lheight = dri->dri_Font->tf_YSize;
					}
					else
					{
						lwidth = IA(GA(o)->GadgetText)->Width;
						lheight = IA(GA(o)->GadgetText)->Height;
					}
					//center the text or image inside the gadget
					xoffset = left + ((width - lwidth) / 2);
					yoffset = top + ((height - lheight) / 2);
					//draw whatever it is
					if( (lod->flags & BF_TEXT) )
					{
						SetFont( rp, dri->dri_Font );
						SetAPen( rp, (GA(o)->Flags & GFLG_SELECTED) ? dri->dri_Pens[HIGHLIGHTTEXTPEN] : dri->dri_Pens[TEXTPEN] );
						Move( rp, xoffset, yoffset + dri->dri_Font->tf_Baseline );
						Text( rp, (STRPTR)GA(o)->GadgetText, strlen( (char *)GA(o)->GadgetText ) );
					}
					else
					{
						DrawImageState( rp, GA(o)->GadgetRender, xoffset, yoffset, (GA(o)->Flags & GFLG_SELECTED) ? IDS_SELECTED : IDS_NORMAL, GPR(msg)->gpr_GInfo->gi_DrInfo );
					}
				}
				//check if we're disabled and draw a ghosting pattern if we are
				if( GA(o)->Flags & GFLG_DISABLED	)
				{
					SetAPen( rp, GPI(msg)->gpi_GInfo->gi_DrInfo->dri_Pens[SHADOWPEN] );
					SetAfPt( rp, ghostdata, 1 );
					RectFill( rp, left, top, left + width - 1, top + height - 1 );
				}
			}
			break;
GM_LAYOUT is an OS 3.x only function but if it is here it won't hurt. Here we use it to automatically resize the framing image, although you can use it for a number of other things. Remember though that you shouldn't do any drawing here since Intuition will call GM_RENDER later, this is just setup so you can do recomputing.
		case GM_LAYOUT:
			{
				WORD width = (GA(o)->Flags & GFLG_RELWIDTH) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->Width : GA(o)->Width;
				WORD height = (GA(o)->Flags & GFLG_RELHEIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->Height : GA(o)->Height;

				lod = INST_DATA( cl, o );

				if( GA(o)->GadgetRender )
				{
					SetAttrs( GA(o)->GadgetRender,
								IA_Width, width,
								IA_Height, height,
								TAG_DONE );
				}
			}
			break;
GM_HITTEST is here so that you can tell Intuition whether the gadget was hit or not and then respond accordingly. Here we just respond that we were in fact hit and we should be activated since it is a square gadget. Although this method can be used so that it check something else to see if it was hit or not (ie. call IM_HITTEST on a BOOPSI image object).
		case GM_HITTEST:
			retval = GMR_GADGETHIT;
			break;
GM_GOACTIVE is here so that you can set things up and do any long time operations before the gadget starts to receive all of the input.device messages. This function has a special return value which tells intuition what it should do with the input event which caused us to go active. They are GMR_NOREUSE which tells Intuition to just kill the event, GMR_REUSE which means it should use the event again (ie. if there was a right mouse button event we would want the menu to show up instead of just trashing it, this can only happen in GM_HANDLEINPUT though), GMR_NEXTACTIVE which is just like pressing tab when a string gadget is selected, GMR_PREVACTIVE which is just like pressing shift tab when a string gadget is selected, and GMR_MEACTIVE which tells Intuition that we are active and want to stay that way. Note that below I should've checked to see if an input event activated us or if it was ActivateGadget(), this should be fixed.
		case GM_GOACTIVE:
			{
				struct RastPort *rp;

				lod = INST_DATA( cl, o );

				//check if we're a push in button and act appropriately
				if( lod->flags & BF_PUSH )
					GA(o)->Flags ^= GFLG_SELECTED;
				else
					GA(o)->Flags |= GFLG_SELECTED;
				//Always, always, always use ObtainGIRPort to get the RastPort,
				//unless of course its a GM_RENDER msg
				if( rp = ObtainGIRPort( GPI(msg)->gpi_GInfo ) )
				{
					DoMethod( o, GM_RENDER, GPI(msg)->gpi_GInfo, rp, GREDRAW_REDRAW );
					ReleaseGIRPort( rp );
				}
				//If its a push in button there is no need to go into GM_HANDLEINPUT
				//so we just return GMR_NOREUSE.
				if( lod->flags & BF_PUSH )
					retval = GMR_NOREUSE;
				else
					retval = GMR_MEACTIVE;
			}
			break;
GM_HANDLEINPUT is where the gadget does all the processing of the input events. Here its pretty simple since we just need to catch timer events and send messages on those and check to make sure the mouse is actually over the gadget.
		case GM_HANDLEINPUT:
			{
				struct RastPort *rp;
				WORD x = GPI(msg)->gpi_Mouse.X;  //these are relative to the upper left corner of the gadget
				WORD y = GPI(msg)->gpi_Mouse.Y;
				struct InputEvent *ie = GPI(msg)->gpi_IEvent;
				BOOL sel = FALSE;
				WORD left = (GA(o)->Flags & GFLG_RELRIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->LeftEdge - 1 : GA(o)->LeftEdge;
				WORD top = (GA(o)->Flags & GFLG_RELBOTTOM) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->TopEdge - 1 : GA(o)->TopEdge;
				WORD width = (GA(o)->Flags & GFLG_RELWIDTH) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->Width : GA(o)->Width;
				WORD height = (GA(o)->Flags & GFLG_RELHEIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->Height : GA(o)->Height;

				lod = INST_DATA( cl, o );

				//check to see if the mouse is over the gadget
				if( (x >= 0) && (x < (width)) &&
					(y >= 0) && (y < (height)) )
				{
					sel = TRUE;
				}
				//on timer events we should send messages to our ICA_TARGET
				//Notice they are OPUF_INTERIM since the user isn't done with us yet
				if( ie->ie_Class == IECLASS_TIMER )
				{
					Notify( cl, o, msg, OPUF_INTERIM, sel, GPI(msg)->gpi_GInfo );
				}
				//this takes care of mouse events
				if( ie->ie_Class == IECLASS_RAWMOUSE )
				{
					switch( ie->ie_Code )
					{
						case SELECTUP:
							//user let up the select button we need to deactivate
							retval = GMR_NOREUSE;
							//if we are selected we need to tell intuition to send a IDCMP_RELVERIFY
							if( GA(o)->Flags & GFLG_SELECTED )
								retval |= GMR_VERIFY;
							//send a final notify, (ie. no OPUF_INTERIM flag)
							Notify( cl, o, msg, 0, sel, GPI(msg)->gpi_GInfo );
							//Set the code field of the IntuiMessage to our GadgetID
							(*GPI(msg)->gpi_Termination) = GA(o)->GadgetID;
							break;
						case MENUDOWN:
							//this is where GMR_REUSE come into play mainly
							retval = GMR_REUSE;
							break;
						default:
							//check if we need to change the graphics of the gadget if
							//the mouse is/isn't over the gadget
							if( (!sel && (GA(o)->Flags & GFLG_SELECTED)) ||
								(sel && !(GA(o)->Flags & GFLG_SELECTED)) )
							{
								GA(o)->Flags ^= GFLG_SELECTED;
								if( rp = ObtainGIRPort( GPI(msg)->gpi_GInfo ) )
								{
									DoMethod( o, GM_RENDER, GPI(msg)->gpi_GInfo, rp, GREDRAW_REDRAW );
									ReleaseGIRPort( rp );
								}
							}
							break;
					}
				}
			}
			break;
GM_GOINACTIVE is where we cleanup after GM_GOACTIVE and GM_HANDLEINPUT. I do the final render here since the gadget could've been inactivated by a select up or a mouse down, so we would've had to write this code for both in GM_HANDLEINPUT.
		case GM_GOINACTIVE:
			{
				struct RastPort *rp;

				lod = INST_DATA( cl, o );

				if( !(lod->flags & BF_PUSH) )
				{
					GA(o)->Flags &= ~GFLG_SELECTED;
					if( rp = ObtainGIRPort( GPI(msg)->gpi_GInfo ) )
					{
						DoMethod( o, GM_RENDER, GPI(msg)->gpi_GInfo, rp, GREDRAW_REDRAW );
						ReleaseGIRPort( rp );
					}
				}
			}
			break;
Finally, if we don't know what the method is we just pass it up to the parent classes.
		default:
			retval = DSM(cl, o, msg);
			break;
	}
	return(retval);
}

SetAttrs()... Doesn't get any better here

The SetClassAttr() function provided in the skeleton class is provided to make it easy to process all of the silly tags. Again notice, no __saveds in the function, although I think it should work with it since its always being called by the dispatcher, but who knows.
ULONG
ASM setClassAttrs( REG(a0) Class * cl, REG(a2) Object * o, REG(a1) struct opSet * msg )
{
	struct localObjData *lod = INST_DATA(cl, o);
	struct TagItem *tags = msg->ops_AttrList;
	struct TagItem *tstate;
	struct TagItem *tag;
	ULONG           tidata;
	BOOL change = FALSE;
	BOOL sizechange = FALSE;

	putreg( REG_A6, cl->cl_UserData );
	geta4();

	/* process rest */
	tstate = tags;
	while (tag = NextTagItem(&tstate))
	{
		tidata = tag->ti_Data;
		switch (tag->ti_Tag)
		{
			//This is a special attribute to tell make the gadget just
			//show the image instead of trying to try the frame
			case BGA_Image:
				GA(o)->GadgetRender = tidata;
				GA(o)->Width = IM(tidata)->Width;
				GA(o)->Height = IM(tidata)->Height;
				break;
			//A text label is wanted, set flags and set change flag
			case GA_Text:
				lod->flags |= BF_TEXT;
				change = TRUE;
				break;
			//An image for the label is wanted, clear flag and set change flag
			case GA_LabelImage:
				lod->flags &= ~BF_TEXT;
				change = TRUE;
				break;
			case GA_DrawInfo:
				lod->dri = (struct DrawInfo *)tidata;
				break;
			//programmatic size change, do a redraw
			case GA_Width:
			case GA_Height:
				sizechange = TRUE;
				break;
			//flag thingies, should prolly use one of the utility.library functions.
			case BGA_Push:
				if( tidata )
					lod->flags |= BF_PUSH;
				else
					lod->flags &= ~BF_PUSH;
				break;
			//dunno what it is
			default:
				break;
		}
	}
	//Check for size change, change the frame's rectangle, should prolly do a GM_RENDER here, oops
	if( sizechange )
	{
		if( GA(o)->GadgetRender )
		{
			SetAttrs( GA(o)->GadgetRender,
						IA_Width, GA(o)->Width,
						IA_Height, GA(o)->Height,
						TAG_DONE );
		}
	}
	//Theres some new text gotta adjust the size.  There should be some flags here
	//so that the programmer can stop the resize from occurring
	if( change && GA(o)->GadgetText )
	{
		struct IBox cont, frame;
		WORD width, height;

		//check for text/image label and find the size
		if( (lod->flags & BF_TEXT) )
		{
			struct DrawInfo *dri = lod->dri;

			width = myTextLength( dri->dri_Font, (char *)GA(o)->GadgetText, strlen( (char *)GA(o)->GadgetText ) );
			height = dri->dri_Font->tf_YSize;
		}
		else
		{
			width = IA(GA(o)->GadgetText)->Width;
			height = IA(GA(o)->GadgetText)->Height;
		}
		//try and do an IM_FRAMEBOX to figure out how big the frame wants to be for this size graphic
		cont.Width = width;
		cont.Height = height;
		if( GA(o)->GadgetRender )
		{
			frame.Left = 0;
			frame.Top = 0;
			frame.Width = width;
			frame.Height = height;
			DoMethod( GA(o)->GadgetRender, IM_FRAMEBOX, &frame, &cont, lod->dri, 0 );
		}
		//GREL_ flags should be check above
		if( !(GA(o)->Flags & GFLG_RELWIDTH) )
			GA(o)->Width = cont.Width;
		if( !(GA(o)->Flags & GFLG_RELHEIGHT) )
			GA(o)->Height = cont.Height;
		//Adjust the frame size, dunno if we wanna redraw here or not since the
		//programmer might want to manually do it so a redraw would be ugly
		if( GA(o)->GadgetRender )
		{
			SetAttrs( GA(o)->GadgetRender,
								IA_Width, cont.Width,
								IA_Height, cont.Height,
								TAG_DONE );
		}
	}
	return (1L);
}

GetClassAtr... Just pure fat.

This is currently of no use to us here, no reason to take it out though.
ULONG
ASM getClassAttr( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opGet * msg )
{
	struct localObjData *lod = INST_DATA(cl, o);

	putreg( REG_A6, cl->cl_UserData );
	geta4();

	switch (msg->opg_AttrID)
	{
		default:
			return ((ULONG) DSM(cl, o, (Msg)msg));
	}
	return (1L);
}

myTextLength... Ooooh Baaaad...

This is prolly bad but I hate having to make a RastPort just to get a text length.
LONG myTextLength( struct TextFont *font, char *str, int len )
{
	int lpc;
	LONG width = 0;
	int currch;

	if( font->tf_Flags & FPF_PROPORTIONAL )
	{
		for( lpc = 0; lpc < len; lpc++ )
		{
			currch = str[lpc] - font->tf_LoChar;
			width += ((WORD *)font->tf_CharSpace)[currch] + ((WORD *)font->tf_CharKern[currch]);
		}
	}
	else
	{
		width = font->tf_XSize * len;
	}
	return( width );
}

Notify... Easy to do but hard in concept

The OM_NOTIFY/OM_UPDATE connection is something I will never understand. Anyways we need to send an OM_UPDATE to our target with our GA_ID as the attribute. The ti_Data of this attribute reflects whether the mouse is over the button or not.
void ASM Notify( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opSet *msg, REG(d1) ULONG flags, REG(d2) sel, REG(a3) struct GadgetInfo *ginfo )
{
	struct TagItem tt[2];
	struct localObjData *lod = INST_DATA(cl, o);

	putreg( REG_A6, cl->cl_UserData );
	geta4();

	tt[0].ti_Tag = GA_ID;
	tt[0].ti_Data = sel ? GA(o)->GadgetID : -GA(o)->GadgetID;

	tt[1].ti_Tag = TAG_DONE;

	DoSuperMethod( cl, o, OM_NOTIFY, tt, ginfo, flags );
}

Maintained by Tim Stack(stack@cs.utah.edu)
Last Changed on 27-Dec-1997, 10:12