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.
#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
#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
#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)
//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 );
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} };
#define MYCLASSID "bbbuttongadget" #define SUPERCLASSID "gadgetclass"
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 ); }
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); }
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); }
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); }
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 ); }
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 ); }