Copyright (c) Hyperion Entertainment and contributors.
BOOPSI 101
In this section we will explore a simple example of creating, interacting with and disposing a simple BOOPSI object - a requester window. With these simple objects you can see the basic operations that are common to almost all BOOPSI objects.
Contents
Creating an Object
The Intuition functions NewObject() / NewObjectA() create a BOOPSI object using either "stack" of tags or predefined a tag list and returns a pointer to a new BOOPSI object:
Object *object = NewObject(Class *cl, ClassID classID, Tag tag1, ...); Object *object = NewObjectA(Class *cl, ClassID classID, const struct TagItem *tags);
As mentioned
In general, BOOPSI objects are "black boxes". This means the inner workings of BOOPSI objects are not visible to the application programmer, so the programmer does not know what goes on inside it. This really means the inner workings of these objects are none of your business. Unless otherwise documented, only use an object pointer as a handle to the object.
To create an object, NewObjectA() needs to know what class the new object is an instance of. To create a public class object, pass a NULL pointer in privclass and an ASCII string in pubclass naming the object's public class. The privclass pointer is used to create a private class object, which is covered in the "Creating a BOOPSI Class" section later in this chapter.
The myattrs tag list is a list of tag/value pairs, each of which contains an initial value for some object attribute. Most objects have attributes which define the characteristics of the object to be created and used which are set by using specifically named tags. Many of these attributes of BOOPSI gagdets and images are derived from the old Intuition Gadget and Image structures (position, size, etc).
If we were to use the above NewObjectA function with a tag list to create a string object, a code excerpt would look like this:
struct TagItem tags[] = { {GA_ID, 1}, {GA_Left, 0}, {GA_Top, 0}, {STRINGA_LongVal, 100}, {TAG_END,0} }; mystringgagdet = IIntuition->NewObjectA(NULL, "string.gadget", (struct TagItem *) tags);
Instead of declaring or allocating and initializing a tag list and caliing NewObjectA() as above, most applications use the stack-based version of the function: NewObject(). As such, the following code sample builds a BOOPSI string gadget on the stack:
mystringgadget = IIntuition->NewObject(NULL, "string.gadget", GA_ID, 1, GA_Left, 0, GA_Top, 0, STRINGA_LongVal, 100, TAG_END);
If NewObject() is successful, it returns a pointer to a new BOOPSI gadget object. Otherwise, it returns NULL. The class "string.gadget" is one of the public classes built into ReAction. It is a class of string gadgets.
If you look at the diagram of the public classes built into Intuition, you'll see that strgclass is a subclass of gadgetclass. In the example above, the attribute tag IDs that start with "GA_" are defined by gadgetclass and not by strgclass. This is because strgclass inherits these attributes from its superclass, gadgetclass. The other attribute, STRINGA_LongVal, is defined by strgclass. It does two things. First, it tells the object that it is a special type of string gadget which only handles an integer value rather than a generic ASCII string. Second, it passes the object its initial integer value.
Disposing of an Object
When an application is done with an object it has to dispose of the object. To dispose of an object, use the Intuition function DisposeObject():
VOID DisposeObject(APTR boopsiobject);
where boopsiobject is a pointer to the BOOPSI object to be disposed. Note that some classes allow applications to connect child objects to a parent object so that when the application deletes the parent object, it automatically disposes of all of its children. Be careful not to dispose of an object that has already been disposed.
Setting an Existing Object's Attributes
An object's attributes are not necessarily static. An application can ask an object to set certain object attributes using the SetAttrs() function:
ULONG SetAttrs(APTR myobject, Tag1, Value1, ...);
Because BOOPSI gadgets require some extra information about their display, they use a special version of this function, SetGadgetAttrs():
ULONG SetGadgetAttrs(struct Gadget *myobject, struct Window *w, struct Requester *r, Tag1, Value1, ...);
Here myobject is a pointer to the BOOPSI object, w points to the gadget's window, r points to the gadget's requester, and the tag/value pairs are the attributes and their new values. The return value of SetAttrs() and SetGadgetAttrs() is class specific. In general, if the attribute change causes a visual change to some object, the SetAttrs() / SetGadgetAttrs() function should return a non-zero value, otherwise, these functions should return zero (see the BOOPSI Class Reference for information on the return values for specific classes). The following is an example of how to set the current integer value and gadget ID of the gadget created in the NewObject() call above:
IIntuition->SetGadgetAttrs(mystringgadget, mywindow, NULL, STRINGA_LongVal, 75, GA_ID, 2, TAG_END);
This changes two of mystringgadget 's attributes. It changes the gadget's current integer value to 75 and it changes the gadget's ID number to 2.
Note that it is not OK to call SetGadgetAttrs() on a BOOPSI object that isn't a gadget, nor is it OK to call SetAttrs() on a BOOPSI gadget.
Not all object attributes can be set with SetGadgetAttrs() / SetAttrs(). Some classes are set up so that applications cannot change certain attributes. For example, the imagery for the knob of a proportional gadget cannot be altered after the object has been created. Whether or not a specific attribute is "settable" is class dependent. For more information about the attributes of specific classes, see the BOOPSI Class Reference.
Getting an Object's Attributes
The Intuition function GetAttr() asks an object what the value of a specific attribute is:
ULONG GetAttr(ULONG attrID, APTR myobject, ULONG *mydata);
where attrID is the attribute's ID number, myobject is the object to get the attribute from, and mydata points to a data area that will hold the attribute value. This function returns a 0 if the object doesn't recognize the attribute, otherwise it returns some non-zero value, the meaning of which depends on the class. In most cases, GetAttr() returns a 1 when it is successful.
Not all object attributes are obtainable using the GetAttr() function. Some classes are set up so that applications cannot query the state of certain attributes. For example, using the GA_Image attribute, an application can give a BOOPSI prop gadget (propgclass) an Image structure which the gadget uses as the imagery for its knob. This attribute is not "gettable" as there is no need for an application to have to ask the gadget for the structure that the application passed it in the first place. Whether or not a specific attribute is "gettable" is class dependent. For more information about the attributes of specific classes, see the BOOPSI Class Reference.
Passing messages to an Object
To pass a message to an object use the function IDoMethodA(), or its stack-based equivalent, IDoMethod():
ULONG IDoMethodA(Object *myobject, Msg boopsimessage); ULONG IDoMethod(Object *myobject, ULONG methodID, ...);
The return value is class-dependent. The first argument to both of these functions points to the object that will receive the BOOPSI message.
For IDoMethodA(), boopsimessage is the actual BOOPSI message. The layout of it depends on the method. Every method's message starts off with an Msg (from <intuition/classusr.h>):
typedef struct { ULONG MethodID; /* Method-specific data may follow this field */ } *Msg;
IDoMethod() uses the stack to build a message. To use IDoMethod(), just pass the elements of the method's message structure as arguments to IDoMethod() in the order that they appear in the structure. For example, to ask the BOOPSI object myobject to add the object addobject to its personal list:
IIntuition->IDoMethod(myobject, OM_ADDMEMBER, addobject);
Many times, additional context information will be needed for an object to visually render itself while processing a message.
LONG DoGadgetMethodA(struct Gadget *object, struct Window *win, struct Requester *req, Msg msg); LONG DoGadgetMethod(struct Gadget *object, struct Window *win, struct Requester *req, ULONG methodID, ...);
This function is similar to IDoMethod(), except DoGadgetMethod() passes along the object's graphical environment in the form of a GadgetInfo structure. DoGadgetMethodA() gets this structure from the gadget's window (or requester). The GadgetInfo structure is important because a Boopsi gadget needs the information in that structure to render itself. Note that if you give DoGadgetMethodA() a NULL Window and NULL Requester pointer, DoGadgetMethodA() will pass a NULL GadgetInfo pointer.
The name and first parameter of DoGadgetMethodA() may lead you to believe that this function applies only to gadgets. Although you can certainly use this function on gadget objects, the function is not restricted to gadget objects. You can use this function on any BOOPSI object. This is important for an object such as a model object, which may react to any BOOPSI message by invoking some method on gadget objects connected to it. Because the model object receives environment information in the form of a GadgetInfo structure, the model can pass that information on to any objects connected to it.
Before this function, passing the environment information was not that easy. A good example of this is the rkmmodelclass example. In that example, the rkmmodelclass is a subclass of modelclass. The rkmmodelclass object inherits the behavior of modelclass, so its sends updates to its member objects. One feature rkmmodelclass adds to modelclass is that these objects maintain an internal integer value. If that value changes, the rkmmodelclass object propagates that change to its member objects.
If one of the member objects happens to be a gadget object, changing the rkmmodelclass object's internal value may change the visual state of the gadgets. For the gadget's to update their visual state, they need the environment information from the GadgetInfo structure as well as the new internal value of the rkmmodelclass object. If an application used DoMethod() or SetAttrs() to set the rkmmodelclass object's internal value, the rkmmodelclass object would not get a GadgetInfo structure. When the rkmmodelclass object propagates the internal value change to its member objects, it has no environment information to pass on to its member objects. As a result, the member gadgets can not update their visual state directly. This is particularly annoying for a propgclass object, because the visual state of the propgclass object can depend on the rkmmodelclass object's integer value.
DoGadgetMethodA() corrects this problem. It passes a pointer to a GadgetInfo structure for the window (or requester) you pass to it. If you plan on implementing new BOOPSI methods that need a GadgetInfo structure in their BOOPSI message, make sure the second long word of the BOOPSI message is a pointer to the GadgetInfo structure. DoGadgetMethodA() assumes that every method (except for OM_NEW, OM_SET, OM_NOTIFY, and OM_UPDATE; see the next paragraph) expects a GadgetInfo pointer in that place.
If you use DoGadgetMethodA() to send an OM_SET message to a BOOPSI gadget, DoGadgetMethodA() passes a pointer to a GadgetInfo structure as the third long word in the BOOPSI message. DoGadgetMethodA() is smart enough to know that the OM_SET method uses an opSet structure as its message, which has a pointer to a GadgetInfo structure as its third long word. DoGadgetMethodA() passes a GadgetInfo structure as the third parameter for the OM_NEW, OM_SET, OM_NOTIFY, and OM_UPDATE methods. For all other methods, like the gadgetclass methods, DoGadgetMethodA() passes a GadgetInfo structure as the second long word. For more information, see the Autodoc for DoGadgetMethodA() and its varargs equivalent, DoGadgetMethod().
What About the BOOPSI Messages and Methods?
According to the "OOP Overview" section, for an object to perform a method, something has to pass it a BOOPSI message. The previous section discussed using Intuition functions to ask an object to do things like set and get attributes. The functions in the previous section seem to completely ignore all that material about methods and messages. What happened to the methods and messages?
Nothing; these functions don't ignore the OOP constructs, they just shield the programmer from them. Each of these functions corresponds to a BOOPSI method:
NewObject() | OM_NEW |
DisposeObject() | OM_DISPOSE |
SetAttrs()/SetGadgetAttrs() | OM_SET |
GetAttr() | OM_GET |
RefreshSetGadgetAttrs() | OM_SET, GM_RENDER |
These methods are defined on the rootclass level, so all BOOPSI classes inherit them. The Intuition functions that correspond to these methods take care of constructing and sending a BOOPSI message with the appropriate method ID and parameters.