Copyright (c) Hyperion Entertainment and contributors.

Difference between revisions of "BOOPSI 101"

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
m
m (Minor corrections (typos, dead link).)
 
(27 intermediate revisions by 3 users not shown)
Line 1: Line 1:
  +
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. When using BOOPSI gadget objects there may be some changes in syntax (f.e., using '''SetGadgetAttrs()''' instead of '''SetAttrs()'''), but the concepts remain the same.
   
  +
== Creating an Object ==
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.
 
 
==== 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:
 
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:
Line 11: Line 10:
 
</syntaxhighlight>
 
</syntaxhighlight>
   
As mentioned previously (see [[BOOPSI#OOPSI_and_Tags|BOOPSI and Tags]]), the '''NewObject()''' function has a variant that uses predefined tag lists ('''NewObjectA()'''), but here we will continue with the more commonly used stack based variant.
+
As mentioned previously (see [[BOOPSI#BOOPSI_and_Tags|BOOPSI and Tags]]), the '''NewObject()''' function has a variant that uses predefined tag lists ('''NewObjectA()'''), but here we will continue with the more commonly used stack-based variant.
   
 
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.
 
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, '''NewObject()''' first needs to know whether we are creating an object from a public or private class. Typically any objects created from system gadgets are all public classes. In such cases, the '''cl''' argument (a pointer) would be set to NULL and '''classID''' argument set to the text name of the class (like "requesterclass" or "string.gadget"). If one wanted to create an object of a private class, the '''cl''' argument would be a pointer to that class and the '''classID''' would be ignored.
+
To create an object, '''NewObject()''' first needs to know whether we are creating an object from a public or private class. Typically any objects created from system gadgets are all public classes. In such cases, the '''cl''' argument (a class pointer) would be set to NULL and '''classID''' argument set to the public name of the class (a text string like "requesterclass" or "string.gadget"). If one wanted to create an object of a private class, the '''cl''' argument would be a pointer to that class and the '''classID''' would be ignored.
   
 
Regardless of whether the tag list is defined elsewhere or within this function call, some list of "tags" is next proivided to define the characteristics of the new object. Each "tag" consists of a pair of values - each with specifically named "attribute" and it's proposed "value". Some general examples of such tags might be:
 
Regardless of whether the tag list is defined elsewhere or within this function call, some list of "tags" is next proivided to define the characteristics of the new object. Each "tag" consists of a pair of values - each with specifically named "attribute" and it's proposed "value". Some general examples of such tags might be:
Line 35: Line 34:
 
</syntaxhighlight>
 
</syntaxhighlight>
   
If successful, the '''reqobj''' will contain a pointer to new a BOOPSI requester object. If unsuccessful, '''reqobj''' will be NULL. This is a place for a developer to apply some error trapping - maybe our application ran out of memory?
+
If successful, the variable '''reqobj''' will contain a pointer to new a BOOPSI requester object. If unsuccessful, '''reqobj''' will be NULL. This is a place for a developer to apply some error trapping when an object couldn't be created - maybe our application ran out of memory? Maybe some of the parameters (tags) were invalid?
   
Even while the requester object may have been created, we won't see anything yet. Our new object is waiting for a BOOPSI message to show itself.
+
Once the requester object has been created, we still won't see anything yet. Our new object is waiting in the wings for a BOOPSI message to show itself. This leads us to the next sections where we interact with the object we created.
   
  +
== Passing messages to an Object ==
=========================
 
UNDER CONSTRUCTION
 
=========================
 
   
  +
To communicate with BOOPSI objects we send "BOOPSI Messages". Unlike traditional AmigaOS "Messages" used by the Exec, ARexx and for interprocess communications, BOOPSI messages are different and frequently rely on tag lists for their content. To "send" a BOOPSI Message to an existing object, we use the '''IDoMethod''' function (or its tag list based variant):
==== 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:
 
   
 
<syntaxhighlight>
 
<syntaxhighlight>
ULONG SetAttrs(APTR myobject, Tag1, Value1, ...);
+
ULONG IDoMethod(Object *myobject, ULONG methodID, ...);
  +
ULONG IDoMethodA(Object *myobject, Msg boopsimessage);
 
</syntaxhighlight>
 
</syntaxhighlight>
   
  +
The return value is class-dependent. The first argument to both of these functions points to the object that will receive the BOOPSI message.
Because BOOPSI gadgets require some extra information about their display, they use a special version of this function, '''SetGadgetAttrs()''':
 
  +
  +
'''IDoMethod()''' then depends on the class and method being used for the contents of the rest of the message. Those elements may include a method name, tags, text or pointers. The class documentation will described those as well as whether the class expects any certain order of tags, etc.
  +
  +
For a simple example, we can ask the BOOPSI requester object '''reqobj''' (created above) to show itself by sending this message:
   
 
<syntaxhighlight>
 
<syntaxhighlight>
  +
result = IIntuition->IDoMethod( reqobj, RM_OPENREQ, NULL, NULL, NULL );
ULONG SetGadgetAttrs(struct Gadget *myobject, struct Window *w, struct Requester *r, Tag1, Value1, ...);
 
 
</syntaxhighlight>
 
</syntaxhighlight>
   
  +
In this case, the '''RM_OPENREQ''' method of the Requester Class expects the elements of its '''orRequest''' structure. The class docs point us to a header file that explains we need to start with the '''MethodID''' and then pointers to a tag list, window and a screen structures. In our simple example above, we are passing no arguments, so our call has three NULL's for these elements.
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|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:
 
  +
  +
When one calls ''IDoMethod()''' with the '''RM_OPENREQ''' method on a requester object, the requester appears and the application waits for the user input. Given the simple "information" style requester we created (with default buttons), the result will simply numerically indicate which button was selected by the user.
  +
  +
With the '''IDoMethodA()''' variant, all of the above arguments have to be combined into a complete BOOPSI Message structure, '''boopsimessage'''. Again the layout of depends on the class and method. Every method's message starts off with a '''Msg''' (from &lt;intuition/classusr.h&gt;) and is followed by the class specific tags:
   
 
<syntaxhighlight>
 
<syntaxhighlight>
  +
typedef struct {
IIntuition->SetGadgetAttrs(mystringgadget, mywindow, NULL,
 
  +
ULONG MethodID; /* Method-specific data may follow this field */
STRINGA_LongVal, 75,
 
  +
} *Msg;
GA_ID, 2,
 
TAG_END);
 
 
</syntaxhighlight>
 
</syntaxhighlight>
   
  +
As mentioned above, there are variants of the '''IDoMethod()''' functions for specific uses, such as the '''DoGadgetMethod()''' function. In that case, the function passes additional information pertinent to GUI gadget objects.
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.
 
   
  +
== Setting an Object's Attributes ==
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.
 
   
  +
Every BOOPSI object is defined by a number of attributes. In the above example we defined our requester when we created the object with '''NewObject()'''. But an object's attributes are not necessarily static. An application can use the '''SetAttrs()''' function to change or set other attributes on an object:
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|BOOPSI Class Reference]].
 
   
  +
<syntaxhighlight>
==== Getting an Object's Attributes ====
 
  +
uint32 IIntuition->SetAttrs( Object * object, Tag tag1, ... );
  +
uint32 IIntuition->SetAttrsA( Object * object, const struct TagItem * tagList );
  +
</syntaxhighlight>
   
  +
In each case we have to provide a pointer to our object and then tag pairs with the Attribute identifiers and the replacement values. As an example, we can use the '''SetAttrs()''' function to reset the type and contents of our requester object from above and reuse the object to open a second, different requester for the user.
The Intuition function GetAttr() asks an object what the value of a specific attribute is:
 
   
 
<syntaxhighlight>
 
<syntaxhighlight>
  +
strcpy(buffer,"default string");
ULONG GetAttr(ULONG attrID, APTR myobject, ULONG *mydata);
 
  +
result = IIntuition->SetAttrs( reqobj,
  +
REQ_Type, REQTYPE_STRING,
  +
REQ_BodyText, "please enter some text",
  +
REQS_Buffer, buffer,
  +
REQS_MaxChars, sizeof(buffer) - 1,
  +
TAG_END);
  +
result = IIntuition->IDoMethod( reqobj, RM_OPENREQ, NULL, NULL, NULL );
 
</syntaxhighlight>
 
</syntaxhighlight>
   
  +
With these changes, we've converted our simple requester with a couple buttons into a string requester. For any attributes we did not reset here, the previous settings or the default values of the class will continue to be used.
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 can be set with '''SetAttrs()''' and this is dependent on each class. For more information about which attributes can be set in specific classes, see the [[BOOPSI_Class_Reference|BOOPSI Class Reference]] and the class AutoDocs and look for those attributes with a '''OM_SET''' listed as an "applicability".
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|BOOPSI Class Reference]].
 
   
  +
Again there is variant of the '''SetAttrs()''' function to be used with BOOPSI GUI gadgets: '''SetGadgetAttrs()'''.
==== Passing messages to an Object ====
 
   
  +
== Getting an Object's Attributes ==
To pass a message to an object use the function IDoMethodA(), or its stack-based equivalent, IDoMethod():
 
  +
  +
Just as we can set attributes of an existing BOOPSI object, we also have the ability to get the current settings from a object's attributes. Inquiring about a specific setting is done using the BOOPSI function '''GetAttr()''':
   
 
<syntaxhighlight>
 
<syntaxhighlight>
  +
uint32 attr = GetAttr(uint32 attrID, Object *object, uint32 *storage);
ULONG IDoMethodA(Object *myobject, Msg boopsimessage);
 
ULONG IDoMethod(Object *myobject, ULONG methodID, ...);
 
 
</syntaxhighlight>
 
</syntaxhighlight>
   
  +
We start by specifying the '''attrID''' is the attribute's identifier we are interested in, we then have to identify object to get the attribute from ('''object'''), and finally provide an area that will hold the value of the attribute. This function returns a 0 if the object doesn't recognize or return 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.
The return value is class-dependent. The first argument to both of these functions points to the object that will receive the BOOPSI message.
 
   
  +
In the case of our requester example, there are only a few attributes to which values can be obtained. The following example will obtain the result code from the last use of the object:
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 &lt;intuition/classusr.h&gt;):
 
   
 
<syntaxhighlight>
 
<syntaxhighlight>
  +
uint32 lastresult;
typedef struct {
 
  +
result = IIntuition->GetAttr(REQ_ReturnCode, reqobj,&lastresult);
ULONG MethodID; /* Method-specific data may follow this field */
 
} *Msg;
 
 
</syntaxhighlight>
 
</syntaxhighlight>
   
  +
Another example of the '''GetAttr''' function would be to retrieve the value a user provided in an Interger type requester. This information could be restrieved like this:
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:
 
   
 
<syntaxhighlight>
 
<syntaxhighlight>
  +
int32 userInt;
IIntuition->IDoMethod(myobject, OM_ADDMEMBER, addobject);
 
  +
result = IIntuition->GetAttr(REQI_Number, reqobj,&userInt);
 
</syntaxhighlight>
 
</syntaxhighlight>
   
  +
While the simple requester class is not a great example for getting a lot of attributes, BOOPSI provides a second pair of functions that uses taglists for obtaining multiple attributes in one call: '''GetAttrs()''' and '''GetAttrsA''':
Many times, additional context information will be needed for an object to visually render itself while processing a message.
 
   
 
<syntaxhighlight>
 
<syntaxhighlight>
  +
uint32 retval = GetAttrs(Object *object, Tag tag1, ...);
LONG DoGadgetMethodA(struct Gadget *object, struct Window *win, struct Requester *req, Msg msg);
 
  +
uint32 retval = GetAttrsA(Object *object, struct TagItem *attrs);
LONG DoGadgetMethod(struct Gadget *object, struct Window *win, struct Requester *req, ULONG methodID, ...);
 
 
</syntaxhighlight>
 
</syntaxhighlight>
   
  +
As with the previous tag functions mentioned above, these functions are the same except for how the tag list is handled. In the case of each tag pair, the value will be set to the current value in the object queried.
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.
 
   
  +
As with setting attributes, not all object attributes are obtainable using the '''GetAttr()''' function, depending on each class. For more information about which attributes can be obtained in specific classes, see the [[BOOPSI_Class_Reference|BOOPSI Class Reference]] and the class AutoDocs and look for those attributes with a '''OM_GET''' listed as an "applicability".
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.
 
   
  +
Unlike when creating and setting attributes on BOOPSI GUI gadgets, there are no GUI gadgets specific versions of the '''GetAttr''' family of functions. Instead, the '''GetAttr''' functions can also be used to obtain GUI gadget attributes where they are available (see the Class Reference and autodocs).
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.
 
   
  +
== Disposing of an Object ==
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.
 
   
  +
When an application is finally done with an object, it has to dispose of the object. To dispose of an object, use the Intuition function '''DisposeObject()''':
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.
 
   
  +
<syntaxhighlight>
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
 
  +
void DisposeObject(Object *obj);
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().
 
  +
</syntaxhighlight>
   
  +
As usual, we use a pointer to our object to indicate the object to be disposed.
==== Disposing of an Object ====
 
   
  +
In the case of our requester example, we would simply call:
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()''':
 
   
 
<syntaxhighlight>
 
<syntaxhighlight>
VOID DisposeObject(APTR boopsiobject);
+
IIntuition->DisposeObject(reqobj);
 
</syntaxhighlight>
 
</syntaxhighlight>
   
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.
+
In many cases - complex gadget GUI's being the prime example - when a BOOPSI object has connect "child" objects and it is disposed, then all children are also disposed. An example would be a window object and all the gadgets within that window. In any case, you must be careful not to dispose of an object that already been disposed.
 
 
   
==== What About the BOOPSI Messages and Methods? ====
+
== What About the BOOPSI Messages and Methods? ==
   
  +
In the above sections of this page, the functions used reflect BOOPSI messages and the underlying basic "methods" performed by a BOOPSI object. In the [[BOOPSI#OOP_Overview|"OOP Overview"]] section we see how these saw how these common methods are practically universal to all BOOPSI classes. The above functions simply provide a wrapper for those common methods like this:
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:
 
   
 
{| class="wikitable"
 
{| class="wikitable"
 
| NewObject() || OM_NEW
 
| NewObject() || OM_NEW
|-
 
| DisposeObject() || OM_DISPOSE
 
 
|-
 
|-
 
| SetAttrs()/SetGadgetAttrs() || OM_SET
 
| SetAttrs()/SetGadgetAttrs() || OM_SET
Line 159: Line 165:
 
| GetAttr() || OM_GET
 
| GetAttr() || OM_GET
 
|-
 
|-
  +
| DisposeObject() || OM_DISPOSE
| 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.
 
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.
  +
  +
One last function had a more direct relationship to operation and methods of BOOPSI objects - in this case, causing our requester object to present itself :
  +
  +
{| class="wikitable"
  +
| IDoMethod() || RM_OPENREQ
  +
|}
  +
  +
Of course, in the case of different classes and objects, the method would change with how the '''IDoMethod''' function was called on those objects.
  +
  +
== Example Program ==
  +
  +
Below is the complete example program that includes the above excerpts. This program will create a requester object, display a requester with buttons and one with a string gadget and print the results in the shell.
  +
  +
<syntaxhighlight>
  +
/* Requesters Example
  +
gcc -o Requesters Requesters.c -lauto
  +
quit
  +
*/
  +
  +
/*
  +
*
  +
************************************************************
  +
**
  +
** Created by: CodeBench 0.31 (20.5.2013)
  +
**
  +
** Project: OS4ex-Requesters
  +
**
  +
** A simple requester.class example used to demonstrate functionality.
  +
**
  +
** File: Requesters
  +
**
  +
** Date: 20-04-2013 20:43:23
  +
**
  +
** Version: 02
  +
**
  +
************************************************************
  +
*
  +
*/
  +
  +
/* includes
  +
*/
  +
#include <exec/exec.h>
  +
#include <dos/dos.h>
  +
#include <intuition/intuition.h>
  +
#include <classes/requester.h>
  +
  +
#include <proto/intuition.h>
  +
#include <proto/exec.h>
  +
#include <proto/dos.h>
  +
#include <proto/requester.h>
  +
  +
#include <string.h>
  +
  +
/* declarations
  +
*/
  +
Object *reqobj;
  +
struct orRequest reqmsg;
  +
char buffer[100];
  +
uint32 result = 0;
  +
  +
int main()
  +
{
  +
  +
/* create a new requester object
  +
*/
  +
strcpy(buffer,"Welcome to your\nfirst BOOPSI requester");
  +
  +
reqobj = IIntuition->NewObject(NULL,"requester.class",
  +
REQ_Type, REQTYPE_INFO,
  +
REQ_TitleText, "Requesters Example",
  +
REQ_BodyText, buffer,
  +
TAG_END);
  +
  +
// Was the requester object created?
  +
if (reqobj)
  +
{
  +
IDOS->Printf(" Requester object created\n");
  +
/* tell the object to show itself
  +
*/
  +
result = IIntuition->IDoMethod( reqobj, RM_OPENREQ, NULL, NULL, NULL );
  +
// How did things go?
  +
if (result)
  +
IDOS->Printf(" Req #1 - result code = %ld\n",result);
  +
else
  +
IDOS->Printf(" Req #1 - result = 0\n");
  +
  +
/* reset object characteristics
  +
*/
  +
  +
strcpy(buffer,"default string");
  +
result = IIntuition->SetAttrs( reqobj,
  +
REQ_Type, REQTYPE_STRING,
  +
REQ_BodyText, "please enter some text",
  +
REQS_Buffer, buffer,
  +
REQS_MaxChars, sizeof(buffer) - 1,
  +
TAG_END);
  +
  +
/* tell object to show itself again
  +
*/
  +
result = IIntuition->IDoMethod( reqobj, RM_OPENREQ, NULL, NULL, NULL );
  +
// Did we get a good response?
  +
if (result>0)
  +
{
  +
IDOS->Printf(" Req #2 - result code = %ld\n",result);
  +
IDOS->Printf(" Req #2 - result text = %s\n",buffer);
  +
}
  +
else
  +
IDOS->Printf(" Req #2 - result = 0\n");
  +
  +
/* Lets check some object attributes
  +
*/
  +
uint32 lastresult;
  +
  +
result = IIntuition->GetAttr(REQ_ReturnCode, reqobj,&lastresult);
  +
// Did we get some value?
  +
if (result>0)
  +
IDOS->Printf(" Last result code = %ld\n",lastresult);
  +
else
  +
IDOS->Printf(" This result = 0\n");
  +
  +
/* Dispose of our requester object
  +
*/
  +
IIntuition->DisposeObject(reqobj);
  +
}
  +
else
  +
IDOS->Printf(" ERROR: Failed to create Requester object\n");
  +
  +
return RETURN_OK;
  +
}
  +
</syntaxhighlight>

Latest revision as of 22:02, 2 June 2018

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. When using BOOPSI gadget objects there may be some changes in syntax (f.e., using SetGadgetAttrs() instead of SetAttrs()), but the concepts remain the same.

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 previously (see BOOPSI and Tags), the NewObject() function has a variant that uses predefined tag lists (NewObjectA()), but here we will continue with the more commonly used stack-based variant.

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, NewObject() first needs to know whether we are creating an object from a public or private class. Typically any objects created from system gadgets are all public classes. In such cases, the cl argument (a class pointer) would be set to NULL and classID argument set to the public name of the class (a text string like "requesterclass" or "string.gadget"). If one wanted to create an object of a private class, the cl argument would be a pointer to that class and the classID would be ignored.

Regardless of whether the tag list is defined elsewhere or within this function call, some list of "tags" is next proivided to define the characteristics of the new object. Each "tag" consists of a pair of values - each with specifically named "attribute" and it's proposed "value". Some general examples of such tags might be:

     GA_Left,         100,
     REQ_TitleText,   "Testing...",
     WA_DragBar,      TRUE,

In the case of our requester example, we can use the NewObject{} function along with a "stack" of "tags" containing basic parameters to create a new requester object like this:

     reqobj = IIntuition->NewObject(NULL,"requester.class",
          REQ_Type,        REQTYPE_INFO,
          REQ_TitleText,   "Requesters Example",
          REQ_BodyText,    buffer,
          TAG_END);

If successful, the variable reqobj will contain a pointer to new a BOOPSI requester object. If unsuccessful, reqobj will be NULL. This is a place for a developer to apply some error trapping when an object couldn't be created - maybe our application ran out of memory? Maybe some of the parameters (tags) were invalid?

Once the requester object has been created, we still won't see anything yet. Our new object is waiting in the wings for a BOOPSI message to show itself. This leads us to the next sections where we interact with the object we created.

Passing messages to an Object

To communicate with BOOPSI objects we send "BOOPSI Messages". Unlike traditional AmigaOS "Messages" used by the Exec, ARexx and for interprocess communications, BOOPSI messages are different and frequently rely on tag lists for their content. To "send" a BOOPSI Message to an existing object, we use the IDoMethod function (or its tag list based variant):

ULONG IDoMethod(Object *myobject, ULONG methodID, ...);
ULONG IDoMethodA(Object *myobject, Msg boopsimessage);

The return value is class-dependent. The first argument to both of these functions points to the object that will receive the BOOPSI message.

IDoMethod() then depends on the class and method being used for the contents of the rest of the message. Those elements may include a method name, tags, text or pointers. The class documentation will described those as well as whether the class expects any certain order of tags, etc.

For a simple example, we can ask the BOOPSI requester object reqobj (created above) to show itself by sending this message:

result = IIntuition->IDoMethod( reqobj, RM_OPENREQ, NULL, NULL, NULL );

In this case, the RM_OPENREQ method of the Requester Class expects the elements of its orRequest structure. The class docs point us to a header file that explains we need to start with the MethodID and then pointers to a tag list, window and a screen structures. In our simple example above, we are passing no arguments, so our call has three NULL's for these elements.

When one calls IDoMethod()' with the RM_OPENREQ method on a requester object, the requester appears and the application waits for the user input. Given the simple "information" style requester we created (with default buttons), the result will simply numerically indicate which button was selected by the user.

With the IDoMethodA() variant, all of the above arguments have to be combined into a complete BOOPSI Message structure, boopsimessage. Again the layout of depends on the class and method. Every method's message starts off with a Msg (from <intuition/classusr.h>) and is followed by the class specific tags:

typedef struct {
    ULONG MethodID; /* Method-specific data may follow this field */
} *Msg;

As mentioned above, there are variants of the IDoMethod() functions for specific uses, such as the DoGadgetMethod() function. In that case, the function passes additional information pertinent to GUI gadget objects.

Setting an Object's Attributes

Every BOOPSI object is defined by a number of attributes. In the above example we defined our requester when we created the object with NewObject(). But an object's attributes are not necessarily static. An application can use the SetAttrs() function to change or set other attributes on an object:

     uint32 IIntuition->SetAttrs( Object * object, Tag tag1, ... );
     uint32 IIntuition->SetAttrsA( Object * object, const struct TagItem * tagList );

In each case we have to provide a pointer to our object and then tag pairs with the Attribute identifiers and the replacement values. As an example, we can use the SetAttrs() function to reset the type and contents of our requester object from above and reuse the object to open a second, different requester for the user.

     strcpy(buffer,"default string");
     result = IIntuition->SetAttrs( reqobj,
          REQ_Type,				REQTYPE_STRING,
          REQ_BodyText,			"please enter some text",
          REQS_Buffer,			buffer,
          REQS_MaxChars,			sizeof(buffer) - 1,
          TAG_END);
     result = IIntuition->IDoMethod( reqobj, RM_OPENREQ, NULL, NULL, NULL );

With these changes, we've converted our simple requester with a couple buttons into a string requester. For any attributes we did not reset here, the previous settings or the default values of the class will continue to be used.

Not all object attributes can be set with SetAttrs() and this is dependent on each class. For more information about which attributes can be set in specific classes, see the BOOPSI Class Reference and the class AutoDocs and look for those attributes with a OM_SET listed as an "applicability".

Again there is variant of the SetAttrs() function to be used with BOOPSI GUI gadgets: SetGadgetAttrs().

Getting an Object's Attributes

Just as we can set attributes of an existing BOOPSI object, we also have the ability to get the current settings from a object's attributes. Inquiring about a specific setting is done using the BOOPSI function GetAttr():

uint32 attr = GetAttr(uint32 attrID, Object *object, uint32 *storage);

We start by specifying the attrID is the attribute's identifier we are interested in, we then have to identify object to get the attribute from (object), and finally provide an area that will hold the value of the attribute. This function returns a 0 if the object doesn't recognize or return 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.

In the case of our requester example, there are only a few attributes to which values can be obtained. The following example will obtain the result code from the last use of the object:

     uint32 lastresult;
     result = IIntuition->GetAttr(REQ_ReturnCode, reqobj,&lastresult);

Another example of the GetAttr function would be to retrieve the value a user provided in an Interger type requester. This information could be restrieved like this:

     int32 userInt;
     result = IIntuition->GetAttr(REQI_Number, reqobj,&userInt);

While the simple requester class is not a great example for getting a lot of attributes, BOOPSI provides a second pair of functions that uses taglists for obtaining multiple attributes in one call: GetAttrs() and GetAttrsA:

     uint32 retval = GetAttrs(Object *object, Tag tag1, ...);
     uint32 retval = GetAttrsA(Object *object, struct TagItem *attrs);

As with the previous tag functions mentioned above, these functions are the same except for how the tag list is handled. In the case of each tag pair, the value will be set to the current value in the object queried.

As with setting attributes, not all object attributes are obtainable using the GetAttr() function, depending on each class. For more information about which attributes can be obtained in specific classes, see the BOOPSI Class Reference and the class AutoDocs and look for those attributes with a OM_GET listed as an "applicability".

Unlike when creating and setting attributes on BOOPSI GUI gadgets, there are no GUI gadgets specific versions of the GetAttr family of functions. Instead, the GetAttr functions can also be used to obtain GUI gadget attributes where they are available (see the Class Reference and autodocs).

Disposing of an Object

When an application is finally done with an object, it has to dispose of the object. To dispose of an object, use the Intuition function DisposeObject():

void DisposeObject(Object *obj);

As usual, we use a pointer to our object to indicate the object to be disposed.

In the case of our requester example, we would simply call:

     IIntuition->DisposeObject(reqobj);

In many cases - complex gadget GUI's being the prime example - when a BOOPSI object has connect "child" objects and it is disposed, then all children are also disposed. An example would be a window object and all the gadgets within that window. In any case, you must be careful not to dispose of an object that already been disposed.

What About the BOOPSI Messages and Methods?

In the above sections of this page, the functions used reflect BOOPSI messages and the underlying basic "methods" performed by a BOOPSI object. In the "OOP Overview" section we see how these saw how these common methods are practically universal to all BOOPSI classes. The above functions simply provide a wrapper for those common methods like this:

NewObject() OM_NEW
SetAttrs()/SetGadgetAttrs() OM_SET
GetAttr() OM_GET
DisposeObject() OM_DISPOSE

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.

One last function had a more direct relationship to operation and methods of BOOPSI objects - in this case, causing our requester object to present itself :

IDoMethod() RM_OPENREQ

Of course, in the case of different classes and objects, the method would change with how the IDoMethod function was called on those objects.

Example Program

Below is the complete example program that includes the above excerpts. This program will create a requester object, display a requester with buttons and one with a string gadget and print the results in the shell.

/* Requesters Example
gcc -o Requesters Requesters.c -lauto
quit
*/
 
/*
*
************************************************************
**
** Created by: CodeBench 0.31 (20.5.2013)
**
** Project: OS4ex-Requesters
**
** A simple requester.class example used to demonstrate functionality.
**
** File: Requesters
**
** Date: 20-04-2013 20:43:23
**
** Version:   02
**
************************************************************
*
*/
 
/*		includes
 */
#include <exec/exec.h>
#include <dos/dos.h>
#include <intuition/intuition.h>
#include <classes/requester.h>
 
#include <proto/intuition.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/requester.h>
 
#include <string.h>
 
/*		declarations
 */
Object *reqobj;
struct orRequest reqmsg;
char buffer[100];
uint32 result = 0;
 
int main()
{
 
	/*		create a new requester object
	 */
	strcpy(buffer,"Welcome to your\nfirst BOOPSI requester");
 
	reqobj = IIntuition->NewObject(NULL,"requester.class",
		REQ_Type,				REQTYPE_INFO,
		REQ_TitleText,			"Requesters Example",
		REQ_BodyText,			buffer,
		TAG_END);
 
	// Was the requester object created?
	if (reqobj)
	{
		IDOS->Printf("   Requester object created\n");
		/*		tell the object to show itself
		 */
		result = IIntuition->IDoMethod( reqobj, RM_OPENREQ, NULL, NULL, NULL );
		// How did things go?
		if (result)
			IDOS->Printf("  Req #1 - result code = %ld\n",result);
		else
			IDOS->Printf("   Req #1 - result = 0\n");
 
		/*		reset object characteristics
		 */
 
		strcpy(buffer,"default string");
		result = IIntuition->SetAttrs( reqobj,
			REQ_Type,				REQTYPE_STRING,
			REQ_BodyText,			"please enter some text",
			REQS_Buffer,			buffer,
			REQS_MaxChars,			sizeof(buffer) - 1,
			TAG_END);
 
		/*		tell object to show itself again
		 */
		result = IIntuition->IDoMethod( reqobj, RM_OPENREQ, NULL, NULL, NULL );
		// Did we get a good response?
		if (result>0)
		{
			IDOS->Printf("  Req #2 - result code = %ld\n",result);
			IDOS->Printf("  Req #2 - result text = %s\n",buffer);
		}
		else
			IDOS->Printf("   Req #2 - result = 0\n");
 
		/*		Lets check some object attributes
		 */
		uint32 lastresult;
 
		result = IIntuition->GetAttr(REQ_ReturnCode, reqobj,&lastresult);
		// Did we get some value?
		if (result>0)
			IDOS->Printf("   Last result code = %ld\n",lastresult);
		else
			IDOS->Printf("   This result = 0\n");
 
		/*		Dispose of our requester object
		 */
		IIntuition->DisposeObject(reqobj);
	}
	else
		IDOS->Printf("   ERROR: Failed to create Requester object\n");
 
	return RETURN_OK;
}