To allow the user to access the Weight attribute of geometric deformer objects a property gadget class needs to be implemented.
Property Gadgets are connected to a layer object using model-view concept. Property gadgets monitor the SelectList attribute of the layer and allow the user to edit attributes of the selected objects.
When the user selects an object, or an attribute of the selected object is changed, the property gadget will receive UPDATE method. Property gadget should read the the attributes of the selected objects and update its controls to show these values (the property gadget acts as a 'view').
Another job for the property gadget is to allow the user to change the attributes of the selected objects. When the user plays with controls in a property gadget, the gadget should read the new values and set them to the selected objects.
All property gadgets are derived from 'inc/real/gadget/r3prpgad.h' class, which corresponds to the r3prim.h model class - the class from which all geometric objects are derived.
The class hierarchy of the property gadget should correspond to the class hierarchy of the model class. Because our geometric object is derived from 'inc/real/objects/r3construct.h' base class, we will derive our property gadget from the property gadget of the r3construct.h class - the 'inc/real/primgad/p3construct.h'.
The class header file would look as follows:
#ifndef R3_GEOMDEFORMPGAD_H #define R3_GEOMDEFORMPGAD_H #include <real/primgad/p3construct.h> /* super class */ /* * Class ID */ #define R3CLID_GEOMDEFORMPGAD ... /* * Methods */ enum { GEOMDEFM_WEIGHT = R3CL_MTHBASE(R3CLID_GEOMDEFORMPGAD) }; /* registration function */ int RegisterGeomDeformPropertyGadClass(R3APP *app); #endif
Much of the work is already taken care of by our super class so the implementation file is very minimal. Our property gadget just needs one float gadget.
/* propgad.c - property gadget for the geom. deformer */ #include "geomdeform.h" /* our model */ #include "geomdefpg.h" /* our class header */ #include <oops/r3window.h> /* other classes we need */ #include <oops/r3packer.h> /* packer layout manager */ #include <oops/r3checkb.h> /* check box gadget */ #include <real/gadget/r3owutil.h> /* some nice property gadget specific stuff */ /* Instance data */ typedef struct r3idata { R3OBJ *window; /* parent window */ R3OBJ *weight; } R3IDATA; static void *r3propgadm_needsupdate(R3CLASS *cl, R3OBJ *obj, R3INT attrib) { if (!attrib ||attrib == GEOMDEFA_Weight) return (void*)TRUE; return (void*)FALSE; } /* our model has changed, update our gadgets to reflect the model */ static void *r3propgadm_doupdate(R3CLASS *cl, R3OBJ *obj, R3OBJ *sellist) { R3IDATA *self = R3CL_IADDR(cl, obj); R3OWuSetIntGad(sellist, self->weight, GEOMDEFA_Weight, R3GCBA_Checked); return obj; } /* user has played with the Weight gadget, update the selected objects */ static void *rotateprpgm_weight(R3CLASS *cl, R3OBJ *obj, R3BOOL val){ { R3IDATA *self = R3CL_IADDR(cl, obj); R3OBJ *model; R3GetAttrs(obj, R3WGA_Model, &model, R3TAG_END); R3OWuSetInt(model, GEOMDEFA_Weight, val); return NULL; } /* standard R3RM_SET method */ static void *r3rm_set(R3CLASS *cl, R3OBJ *obj, R3TAG *tl, int create) { R3TAG *tag; R3IDATA *self = R3CL_IADDR(cl, obj); void *undef_attr = NULL; while(tag = R3TagNext(&tl)) { switch(tag->ident) { case R3WGA_Parent: if (self->window != tag->value) { self->window = tag->value; if (!create) { if (self->weight) R3SetAttrs(self->weight, tag->ident, tag->value, R3TAG_END); R3DoSuper(cl, obj, R3RM_SET, tag->ident, tag->value, R3TAG_END); } } break; default: if(!create && R3DoSuper(cl, obj, R3RM_SET, tag->ident, tag->value, R3TAG_END)) undef_attr = tag; break; } } return(undef_attr); } /* create method */ static void* r3rm_create(R3CLASS *cl, R3OBJ *obj, R3TAG *tags) { R3IDATA *self; R3OBJ *packer; if(!(obj = R3DoSuperA3(cl, obj, R3RM_CREATE, NULL, NULL, tags))) return NULL; self = R3CL_IADDR(cl, obj); /* ask gmanager and parent window */ R3GetAttrs(obj, R3PRPGADA_Gmanager, &packer, R3WGA_Parent, &self->window, R3TAG_END); /* create check box into the parent window */ if (!(self->weight = R3New(R3CLID_CHECKBOX, R3WGA_Parent, self->window, R3GA_Text, "Weight", R3GA_ToolTip, "Strenght of the deformation effect", R3TAG_END))) goto bail_out; /* insert check box into the geometry manager, this defines appropriate * position for our gadget */ R3Do(packer, R3GMM_INSERT, R3GMA_Slave, self->weight, R3PA_Anchor, R3PAAF_ALIGN, R3TAG_END); /* ask check box to inform us whenever the user plays with it */ R3Do(self->weight, R3WGM_MAPCHANGES, R3WGA_MapToObj, obj, R3WGA_MapToMethod, GEOMDEFPGADM_WEIGHT, R3TAG_END); r3rm_set(cl, obj, tags, TRUE); R3DoA(obj, R3RM_UPDATE, NULL); return(obj); bail_out: R3SetAttrs(obj, R3WGA_Parent, NULL, R3TAG_END); return NULL; } static void r3rm_delete(R3CLASS *cl, R3OBJ *obj) { R3IDATA *self = R3CL_IADDR(cl, obj); if (self->weight) R3Do(self->weight, R3WGM_UNMAPCHANGES, R3WGA_MapToObj, obj, R3WGA_MapToMethod, GEOMDEFPGDADM_WEIGHT, R3TAG_END); } static void *R3Dispatch(R3CLASS *cl, R3OBJ *obj, R3INT method, void *p1, void *p2, void *msg) { switch(method) { case R3RM_CREATE: return r3rm_create(cl, obj, msg); case R3RM_DELETE: r3rm_delete(cl, obj); return R3DoSuperA3(cl, obj, method, p1, p2, msg); case R3RM_SET: return r3rm_set(cl, obj, msg, FALSE); case ROTATEPRPGM_WEIGHT: return geomdefpgadm_weight(cl, obj, (R3FLOAT *)msg); case R3PRPGADM_NEEDSUPDATE: if(r3propgadm_needsupdate(cl, obj, (R3INT)msg)) return (void *)TRUE; return R3DoSuperA3(cl, obj, method, p1, p2, msg); case R3PRPGADM_DOUPDATE: r3propgadm_doupdate(cl, obj, msg); return R3DoSuperA3(cl, obj, method, p1, p2, msg); default: return(R3DoSuperA3(cl, obj, method, p1, p2, msg)); } return(NULL); } int RegisterRotatePropertyGadgetClass(R3APP *apphnd) { static R3APP *app; if(app) return TRUE; app = apphnd; if(!R3RegisterFloatGadgetClass(app)) return(FALSE); if(R3ClassCreate(R3CLID_CONSTRUCTIONGADGET, R3CLID_GEOMDEFORMPGAD, R3Dispatch, sizeof(R3IDATA), R3TAG_END) == NULL) return(FALSE); /* Let property gadget know about us */ R3DoClassA3(R3CLID_PROPERTIESGADGETS, R3PRPGADM_INSTALLPRPGADCLASS, (void*)R3CLID_GEOMDEFORMGAD, (void*)R3CLID_GEOMDEFORM, (void*)R3PRPGADT_SPECIFIC); return(TRUE); }
In the r3rm_create method we call:
R3Do(self->invert, R3WGM_MAPCHANGES, R3WGA_MapToObj, obj, R3WGA_MapToMethod, ROTATEPRPGM_WEIGHT, R3TAG_END);
This asks the check box to send us ROTATEPRPGM_WEIGHT method whenever the user plays with the gadget. Correspondingly we must call R3WGM_UNMAPCHANGES in the r3rm_delete method.
We set the gadget's current value to the selected objects by calling:
R3OWuSetInt(model, GEOMDEFA_Weight, val);
This function locks the layer, scans through the select list and sets the given value 'val' to the selected objects.
The property gaget base class also takes care of setting up the model-view relation ship. In model-view system the R3RM_UPDATE method is sent to view objects to inform the view about attribute changes. However, for optimization reasons, the property base class doesn't pass the R3RM_UPDATE to us directly. Instead, it does it in optimized way, by calling two methods: R3PROPGDM_NEEDSUPDATE and R3PROPGDM_DOUPDATE. In the 'needsupdate' method we specify all attributes and methods that should generate 'doupdate' to our gadget. The actual 'doupdate' method is optimized and sent to us through do-when-idle concept.
So, when the state of our model (selected objects) is changed, we get R3PRPGADM_DOUPDATE method and we should update read the new value of the model attributes and set it to the float gadget.
R3OWuSetIntGad(sellist, self->weight, GEOMDEFA_Weight, R3FLGA_Float);
This is another simple helper function: it scans through the select list and fetch the given attribute GEOMDEFA_Weight from each selected object. If the attribute value is identical, it sets it to the given gadget and clears the 'conflict' state of the gadget. If the value is not identical in the selected objects, the function sets R3GA_Concflict = TRUE and the value of the first selected object is set to the gadget.
It is important to understand that when a property gadget is created and R3WGA_Parent attribute is set, the parent window calls R3RM_REF method of the gadget (see reference counting concept).
If you want to delete the property gadget, you must detach it from its model(s) and set its parent to NULL. This drops the reference count of the gadget to zero and the gadget is deleted.
The final step is plug our property gadget into the Property Window.
In plugin.c/R3InitLibrary() call:
if(R3ClassFind(R3CLID_PROPERTIESGADGETS)) if(RegisterGeomDeformPropertyGadgetClass(app)) R3DoClassA3(R3CLID_PROPERTIESGADGETS, R3PRPGADM_INSTALLPRPGADCLASS, (void*)R3CLID_GEOMDEFORMPGAD, (void*)R3CLID_GEOMDEFORM, (void*)R3PRPGADT_SPECIFIC);
In other words, see if the application in question has a property window. If so, try to register our property gadget class. If succeeded, tell the property window that it should show our gadget whenever the user selects geomdeform objects.
TARGETLIB = geomdeform CFILES = geomdeform.c geomdeformpg plugin.c INCLIBS = \ $(LINKLIBPRE)r3code$(LINKLIBEXT) \ $(LINKLIBPRE)r3obj$(LINKLIBEXT) \ $(LINKLIBPRE)r3gad$(LINKLIBEXT) \ $(LINKLIBPRE)r3primgad$(LINKLIBEXT) \ $(LINKLIBPRE)r3wid$(LINKLIBEXT) !include $(V4HOME)/$(V4MKDLL).mak