@DATABASE AmigaMail @NODE MAIN "IV-23: Introduction to Boopsi" @TOC "Table_of_Contents/IV" By John Orr Boopsi is an acronym for Basic Object Oriented Programming System for Intuition. On its simplest level, boopsi allows the application programmer to create Intuition supported gadgets and images with minimal overhead. It allows a program to consolidate gadgets into one entity to make processing and updating easy. On a more sophisticated level, boopsi provides ways to create a wide variety of system supported, extensible custom gadgets. Understanding boopsi requires an understanding of several of the concepts behind Object Oriented Programming (OOP). This article only briefly covers those concepts. For a more in depth explanation of those concepts, see Timothy Budd's book titled A Little Smalltalk (Addison- Wesley Publishing ISBN 0-201-10698-1). A port (by Bill Kinnersley) of Timothy Budd's Little Smalltalk interpreter is on Fish disk #37. In the boopsi version of the Object Oriented Programming (OOP) model, everything is an Object. Each object is a distinct entity. Certain objects have similar characteristics and can be classified into different groups called classes. As objects, a dog, a cat, and a mouse are all distinct objects but they all have something in common; they can all be classified as animals. A specific object is an instance of a particular class ("Rover" is an instance of class "dog"). Classes can be subdivided into subclasses. A vegetable object class can have several subclasses such as peas, corn, and spinach. Inversely, the superclass of peas, corn, and spinach is "vegetable". In turn, both the "animal" and "vegetable" classes are subclasses. They are subclasses of a universal root category. The OOP language Smalltalk calls this class "Object". Figure 1 - Classes object | ___________|__________ / \\ / \\ animal vegetables | | _____|_____ _____|_____ / | \\ / | \\ / | \\ / | \\ dog cat mouse peas corn spinach In boopsi, the universal root category is called rootclass. Intuition supplies three subclasses of rootclass: gadgetclass, imageclass, and icclass. All boopsi gadgets in the system are an instance of gadgetclass. Likewise, all boopsi images are an instance of imageclass. The remaining subclass, icclass, is a concept new to Intuition that allow one boopsi object (such as a gadget) to notify another boopsi object when some specific event occurs. For example, a program can offer a user two methods of altering one integer value; one by sliding a proportional gadget, the other by typing in a value at a string gadget. Without boopsi, the program would have to explicitly update one gadget when the other was altered by the user. Using a boopsi icclass object, the gadgets can update each other. The gadgets update each other automatically, without the calling program's intervention. The rootclass' subclasses each have their own subclasses. These subclasses are discussed later in this article. Figure 2 - The Boopsi Classes rootclass / _____________/____________ / / \\ / / \\ icclass / gadgetclass / / \\ / / _______________\\___________________ / imageclass / / \\ \\ / / / / \\ \\ modelclass / propgclass strgclass buttongclass groupgclass / \\ / \\ _______/___________________________ frbuttongclass / \\ \\ \\ / \\ \\ \\ frameiclass sysiclass fillrectclass itexticlass It is possible for an application to create its own custom boopsi class. When an application creates a class, it can make it either public or private. A public class has a name associated with it (for example gadgetclass) so arbitrary applications can access it. A private class has no name associated with it, so unless an application has a pointer to the class, it cannot access it. Each class has a set of methods associated with it Each method of a class is an operation that applies to objects of that class. A example of a method for a class of integers is to add or to subtract "integer" objects. All boopsi actions are carried out via methods. In OOP terminology, an application requests an object to perform some method by sending the object a message (which is not related to the Exec style message). In boopsi, the amiga.lib function DoMethod() accepts a method ID and some method specific parameters (older versions of amiga.lib do not have DoMethod(), but the function is available on the Atlanta DevCon disks in classface.o). DoMethod() creates a message from these parameters and sends the message to the object: ULONG DoMethod(Object *myobject, ULONG MethodID, ...); where myobject is a pointer to the target object, MethodID specifies which method to perform. Any remaining parameters are passed on to the method in the form of a boopsi message. Each message contains the method ID and the parameters for the method. The parameters are method specific. For example, one way to delete the imageclass object obj2delete is to call the DoMethod() function like this: DoMethod(obj2delete, OM_DISPOSE); The OM_DISPOSE method does not require any arguments, so, in this case, DoMethod() has only two arguments. One peculiar thing about the above function call is that the OM_DISPOSE method is not defined for imageclass. It is instead defined in the superclass of imageclass, rootclass. The OM_DISPOSE method works because imageclass inherits the methods of its superclass, rootclass. If an object is asked to perform a method that its class does not explicitly define, it passes the request onto its superclass for processing. This superclass can in turn pass on the request to its superclass if it does not understand the method requested. Some of the methods currently defined for Boopsi's rootclass are: OM_NEW - Creates a new object. Normally this method is not called directly by the application programmer (a.k.a. using the DoMethod() function). Instead, there is an Intuition function called NewObject() that takes care of creating objects. APTR NewObject( struct IClass *privateclass, UBYTE *publicclassID, unsigned long tag1, ...); The privateclass and publicclassID parameters are used to specify the new object's class. NewObject() only pays attention to one of these. If privateclass is NULL, publicclassID points to a string naming the class of the new object. If privateclass is not NULL, it contains a pointer to a private class. The remaining arguments make up a series of tag ID/data pairs. These are used to set the object's default attributes. These attributes are specific to the class in question. This function returns a handle to the new object. Each object (or instance of a class) has instance data associated with it. The OM_NEW method takes care of allocating memory for the instance data for each class. Instance data is class specific, but all subclasses inherit instance data from their superclasses. For example, part of the instance data for a gadgetclass object is a Gadget structure. Any objects that are instances of subclasses of gadgetclass have a Gadget structure embedded in them. Subclasses can have their own class specific instance data in addition to the instance data inherited from the superclass. Boopsi gadgetclass and imageclass objects are organized so that NewObject() returns a pointer to the corresponding Intuition structure embedded within them (struct Gadget and struct Image, respectively). This makes it possible to use non-boopsi Intuition functions on boopsi objects. Normally, these internal structures should be considered private, and should be accessed through the corresponding attributes, but if it is necessary, an application can look at certain fields in the embedded structure. For both gadgets and images, the Left, Top, Width, and Height fields are legal to look at. The Images NextImage field is also OK for viewing. However, this does not mean that it is OK for an application to go poking around the internals of boopsi objects. All boopsi objects are strictly private and can change without notice. Use only the functions Intuition provides for manipulating boopsi objects. Another function, NewObjectA(), works exactly like NewObject(), but instead of accepting the tag pairs as arguments, it accepts a single pointer to a TagList. See the Intuition Autodocs for more details. OM_DISPOSE - Deletes an object. Like OM_NEW this method is not normally called directly by the application program. Instead there is an Intuition function DisposeObject() that takes care of object disposal. void DisposeObject( APTR object2delete ); OM_SET - Sets object specific attributes. This method is not normally called by the application program directly. Instead, there are two Intuition functions that set object attributes: ULONG SetAttrs( APTR object, unsigned long tag1, ... ); ULONG SetGadgetAttrs( struct Gadget *mygadgetobject, struct Window *window, struct Requester *requester, unsigned long tag1, ... ); SetAttrs() accepts as arguments a pointer to the object in question and a series of tag pairs corresponding to the attributes to set. SetGadgetAttrs() is a special version of SetAttrs() that is required to change the attributes of gadgetclass objects. SetGadgetAttrs() is similar to SetAttrs(), except it has some extra parameters that a gadget needs to redraw itself in response to the attribute changes. This function can be used on non-gadgetclass objects as the gadget specific parameters are ignored by the other object classes. If the gadget is not yet attached to a window or requester, these arguments should be set to NULL. Both of these functions have corresponding TagList based arguments, SetAttrsA() and SetGadgetAttrs(). OM_GET - Reads an object specific attribute. The Intuition function GetAttr() provides easy access to this method: ULONG GetAttr( unsigned long attrID, APTR object, ULONG *storagePtr ); GetAttr() fills in storagePtr with the value of the object's attribute attrID. The function returns FALSE if there is an error. OM_ADDMEMBER - Adds a boopsi object to another object's list, if it has one. Certain object classes have an Exec list as part of their instance data. To add the object object2add to the list of the object mainobject use DoMethod() like so: DoMethod(mainobject, OM_ADDMEMBER, object2add); DoMethod() also has a non-varargs form called DM() (also in amiga.lib). DM() accepts an object pointer and a pointer to a structure specific to a method. For example, the DM() form of the above DoMethod() call would look like this: DM(mainobject, addmemstruct); where addmemstruct is a pointer to the following structure (defined in ): struct opMember { ULONG MethodID; /* in this case MethodID = OM_ADDMEMBER */ Object *opam_Object; /* = addmemstruct */ }; OM_REMMEMBER - Removes a boopsi object previously added with OM_ADDMEMBER. The parameters are the same for OM_ADDMEMBER. OM_UPDATE - Updates attributes of an object. For gadgets, this method is very similar to the OM_SET method. It is normally used only between objects for notifying each other of attribute changes, so simple boopsi users should use the SetAttrs() and SetGadgetAttrs() functions. OM_NOTIFY - Notifies one object when another object's attribute(s) have changed. It is normally used only between objects for modifying each others attributes, so simple boopsi users should use the SetAttrs() and SetGadgetAttrs() functions. @{" Attributes " link IV-23-1} @{" Gadgetclass Subclasses " link IV-23-3} @{" Appendix " link IV-23/ClassDoc.txt/MAIN} @{" Imageclass Subclasses " link IV-23-2} @{" Interconnection " link IV-23-4} @ENDNODE @NODE IV-23-1 "Attributes" All boopsi objects have attributes associated with them. These attributes reflect various properties of a specific object. For example, an image object has attributes such as IA_Left, IA_Top, IA_Width, and IA_Height, all of which correspond to fields in the Image structure. Five methods deal with an object's attributes: OM_NEW sets attributes at object creation (initialization) time OM_SET changes attributes after the object has been created OM_GET reads an object attribute OM_UPDATE updates an object attribute (for use by objects only) OM_NOTIFY notifies one object of changes to another object's attributes Not all of these methods apply to all attributes. Some attributes are not "settable" or not "gettable", for example. See the @{"appendix" link IV-23/ClassDoc.txt/MAIN} at the end of this article to find out which of these methods apply to specific attributes. @ENDNODE @NODE IV-23-2 "Imageclass Subclasses" An imageclass object contains an Image structure that Intuition uses to render objects such as gadgets. Boopsi imageclass objects are organized so that when NewObject() returns a pointer to an imageclass object, it points to the actual Image structure. Normally, an application does not create an imageclass object. Instead, it will use a subclass of imageclass. Currently, there are four subclasses: frameiclass, sysiclass, fillrectclass, and itexticlass. frameiclass - An embossed or recessed rectangular frame, rendered in the proper DrawInfo pens, with enough intelligence to bound or center its contents. sysiclass - The class of system images. The class includes all the images for the system gadgets, and the Gadtools check mark and button glyphs. fillrectclass - Rectangle with frame and pattern support. itexticlass - A specialized image class used for rendering text. Note that you have to calculate itexticlass object's width and height yourself. @ENDNODE @NODE IV-23-3 "Gadgetclass Subclasses" A gadgetclass object contains an Intuition Gadget structure. Like imageclass, applications do not normally create objects of this class, but instead create objects of subclasses of gadgetclass. Currently, gadgetclass has four subclasses: propgclass - An easy to use, one-dimensional proportional gadget. strgclass - A string gadget. groupgclass - A special gadget class that creates one composite gadget out of several others. buttongclass - A repeating boolean button gadget. buttongclass has a subclass of its own: frbuttongclass - A button that outlines its label with a frame. The example @{"boopsi1.c" link IV-23/boopsi1.c/MAIN} (at the end of this article) uses sysiclass images and several boopsi gadgets. The example takes care of processing and updating the gadgets. @ENDNODE @NODE IV-23-4 "Interconnection" The boopsi gadgets in @{"boopsi1.c" link IV-23/boopsi1.c/MAIN} are not very powerful. The gadgets work independently of each other, forcing the application to unify them. It is possible to make gadget objects update each other without the application's intervention. Gadgetclass defines two attributes used in this updating process: ICA_Target and ICA_Map. The ICA_Target attribute specifies a pointer to a target object. If an object's attribute changes (and the OM_NOTIFY method applies to that attribute), the object sends itself an OM_NOTIFY message. If the object has a target object, the target will receive an OM_UPDATE message which usually tells the target to set one of its own attributes. For example, when the user slides the knob of a propgclass object, its PGA_Top attribute changes. If this object has a second propgclass object as its target, the second object will receive an OM_UPDATE message and will set its corresponding PGA_Top attribute to the PGA_Top value of the first propgclass object. Because objects of different classes do not always have corresponding attributes, there is a way to map attributes of one object to the attributes of another. The ICA_Map accepts a TagItem array of attribute pairs. The first entry in the pair is the source attribute ID and the second is the target object's attribute. For example, an ICA_Map that maps a prop gadget's PGA_Top attribute to a string gadget's STRINGA_LongVal attribute would look like this: struct TagItem slidertostring[] = { {PGA_Top, STRINGA_LongVal}, {TAG_END, } }; Note that the OM_UPDATE method has to apply to the target attribute for the change to take place. Although the gadget attributes ICA_Target and ICA_Map allow boopsi gadgets to update each other, by themselves these attributes do not provide the application with any information on changes to the gadgets. Instead of using another gadget as a target object, an object targets an icclass object. Icclass (or InterConnection) objects are simple information forwarders. Icclass defines two attributes: ICA_Target and ICA_Map, which are almost identical to the gadgetclass attributes. The difference is that icclass objects can send the application an IDCMPUPDATE IntuiMessage when it receives an OM_UPDATE. If an icclass object receives an OM_UPDATE, it will send the IntuiMessage only if the icclass object's ICA_Target is ICTARGET_IDCMP and the updated attribute is mapped to a special dummy attribute, ICSPECIAL_CODE. The IntuiMessage's Code field contains the lower 16 bits of the updated attribute. Icclass has a more powerful subclass called modelclass. A modelclass object sends OM_UPDATE messages to an entire list of objects that the modelclass object maintains. This makes it possible for gadgets to update each other and for the application to find out about the changes. A modelclass object "broadcasts" attribute changes to its list of boopsi objects and it also lets the application know about attribute changes because it inherits the ICTARGET_IDCMP mechanism from icclass. To add objects to a modelclass object's list, use the OM_ADDMEMBER method. The power of icclass and modelclass lies in using them to create a custom subclass. Consider a group of prop gadgets each of which is used to control one of a color's R, G, B, H, S, and V components. When the user changes one of the color components with a prop gadget, the custom model will be notified of that change and, because it is customized for this specific purpose, it will recalculate the values for the remaining components. The model will then let the rest of the prop gadget objects (which are attached to icclass objects in the custom model's personal list) know about the changes to their component values so they can move their slider to its new position. All this work is done by the objects themselves; the application does not have to process any of the intermediate input. In general, boopsi's power lies in its Intuition-supported extensibility. Using the existing boopsi classes as a foundation, you can create entirely new subclasses. This makes it possible to create your own custom gadgetry and have it work perfectly with Intuition, just like any existing gadget. New subclasses of modelclass can be used to create gadgets that talk directly to ARexx or the clipboard device. If new classes are general enough, they can be made public so other applications can use them. @ENDNODE