Copyright (c) Hyperion Entertainment and contributors.
ReAction
Contents
Introduction
ReAction is a toolkit for GUI programming in AmigaOS. It is based on Intuition's BOOPSI, an object-oriented philosophy. Understanding the basic concepts of BOOPSI is an important prerequisite for working with ReAction so make sure you have studied the BOOPSI documentation linked above.
Note |
---|
There seems to be a certain degree of confusion as regards the relation between ReAction and BOOPSI. It must be understood that the two are not really interchangeable terms (although they are sometimes used in this way). ReAction is a BOOPSI toolkit so there is a part-whole relation between them. ReAction can’t exist without BOOPSI, but BOOPSI can perfectly exist without ReAction. BOOPSI is a general object-oriented programming framework while ReAction is a set of ready-made classes based on this framework. |
Originally a third-party product, ReAction became part of the operating system in AmigaOS 3.5. The system, whose internal BOOPSI class set had been rather limited, received a comprehensive toolkit covering most GUI programming needs. Over the years ReAction has grown and matured, with many features added. It is now the recommended toolkit for GUI programming under AmigaOS.
Overview
ReAction is:
- object-oriented
- All GUI elements created via the toolkit – windows, gadgets, images, even ARexx ports – are manipulated as objects of a certain class. The class determines the look, function and general properties of the object; objects of the same class serve the same purpose and share the same properties. In a program, a GUI element (object) is an instance of a class, therefore object creation is often referred to as instantiation.
- modular and extensible
- ReAction is implemented as a set of modules (class libraries) that reside on disk. New classes can be written or derived (“subclassed”) from existing classes, thus adding new functionality to the toolkit.
- layout-based
- GUI elements provided by the toolkit are meant to be placed in a layout: a structure determining how objects are positioned, sized and grouped in the GUI. Objects governed by a layout are not programmed for specific positions or sizes; instead, these parameters are automatically decided by the layout according to the properties of the individual objects.
- BOOPSI-compatible
- Program GUIs created through ReAction can include other AmigaOS components based on BOOPSI, such as datatypes or Intuition’s internal BOOPSI classes.
Class Opening and Closing
Classes are, in fact, standard Amiga libraries. The data structure describing a BOOPSI class is called ClassLibrary. As you can see from the C-language definition below, the structure contains the standard struct Library plus some additional fields. This makes it possible to treat classes as normal libraries and, at the same time, support BOOPSI-specific features:
struct ClassLibrary { struct Library cl_Lib; /* Embedded library */ UWORD cl_Pad; /* Align the structure */ Class *cl_Class; /* Class pointer */ };
Like other system libraries, classes must be opened before they are used. An exception to this rule are classes that are part of another component. For example, certain BOOPSI classes are “hardcoded” in Intuition so they can be accessed as soon as you open the Intuition Library. Similarly, in the ReAction toolkit, the Page Gadget class is part of the Layout Gadget binary; therefore, pages can be used once the Layout Gadget class becomes available.
In the past, BOOPSI classes were opened/closed just like you would open/close any other library, that is, through the Exec functions OpenLibrary() and CloseLibrary(). Despite having being shown in numerous code examples, this practice is now considered deprecated and the AmigaOS 4 Intuition received dedicated functions, OpenClass() and CloseClass(). The opening call actually returns two variables in one go: the class library base and the class pointer. When closing the class, the library base is passed as parameter. The class pointer can (and should) be used for object instantiation; see below.
The following code snippet opens and closes the ReAction Layout Gadget class:
struct ClassLibrary *LayoutBase; /* the class library base */ Class *LayoutClass; /* the class pointer */ LayoutBase = IIntuition->OpenClass("layout.gadget", 52, &LayoutClass); /* do something */ IIntuition->CloseClass(LayoutBase);
The number refers to the class version, 52 being a sensible minimum. This particular OpenClass() call returns the Layout Gadget class library base (LayoutBase) and stores the class pointer in the LayoutClass variable. The call, if successful, also sets the cl_Class field in the ClassLibrary data structure (see the definition above) so when it, later, comes to actually using the class pointer, you can either keep the LayoutClass value, or you can retrieve the pointer from the library base (ie. its data structure) like this:
LayoutClass = LayoutBase->cl_Class;
Remember that unlike OpenClass(), the more universal OpenLibrary() function is unaware of BOOPSI-specific features so it will neither return the class pointer, nor set the the cl_Class field in struct ClassLibrary. The following section explains why the class pointer is so important.
Object Creation (Instantiation) and Disposal
In order to create a ReAction GUI element you have to instantiate its object from a particular class. Instantiation is performed through the Intuition function NewObject() which, if successful, returns an object pointer. Internally, the function invokes a method called OM_NEW, which every BOOPSI class must implement and which creates an object instance.
To tell NewObject() which particular class is to be instantiated from, you use either
- the class pointer, obtained from OpenClass() – see above; or
- the class public name, if the class is public. All ReAction classes are public classes.
Using the class pointer is noticeably faster on lower-end systems. Instantiation via public names carries a certain overhead associated with walking through the system class list and processing name strings. For example, if twenty objects of the same class were to be instantiated using the class name, the NewObject() routine would go through the system list no less than twenty times, despite the class being identical! Compared to this, the class pointer is only obtained once – during OpenClass() – and points directly to the particular class without any further lookup. In other words, if you want your GUI to get created as fast as possible, use the class pointer in the NewObject() call whenever you can.
Classes that are part of another component (such as the “internal” BOOPSI classes in Intuition, or the ReAction Page Gadget class) are inherently public and can only be instantiated via their public name. As they are never opened directly (see Class Opening and Closing above), you don’t have access to their class pointer and must, therefore, use the name instead – there is no other way.
The following code instantiates a Layout Gadget object and a Page Gadget object. Both methods are shown here: the layout is created from the class pointer whereas the page is instantiated from the public name (remember that the Page Gadget class code is part of the Layout Gadget binary, so there is no class pointer for the page). When the objects are no longer needed, you are expected to call DisposeObject() to clean up after yourself. The function invokes the OM_DISPOSE method and frees all resources associated with the particular object instance:
Object *LayoutGadget; /* object pointers */ Object *PageGadget; /* Instantiate the layout gadget using the class pointer. The pointer variable, LayoutClass, is taken over from the OpenClass() example in the previous section. */ LayoutGadget = IIntuition->NewObject(LayoutClass, NULL, LAYOUT_Orientation, LAYOUT_ORIENT_VERT, TAG_DONE); /* Instantiate the page gadget using the public class name. */ PageGadget = IIntuition->NewObject(NULL, "page.gadget", TAG_DONE); /* Do something and, ultimately, dispose of both objects. */ IIntuition->DisposeObject(LayoutGadget); IIntuition->DisposeObject(PageGadget);
Objects in Hierarchy
Certain ReAction classes allow (or even expect) their objects to have children objects attached. For example, a Window Class object normally carries a layout object that defines the contents of the window. Similarly, a Page Gadget object typically embeds a layout object defining the page contents. Layouts – hierarchical by nature – also take other objects as children, allowing for simple as well as complex GUI structures spanning several object generations.
Objects with children objects attached to them are disposed of together with all their children, including their children’s children (should there be any). In other words, only the parent object is disposed of. This feature is illustrated by the following example, in which the page gadget receives a child object – a layout gadget; this entire parent-child structure is subsequently attached to another layout as its child object. As you can see, only one DisposeObject() call is necessary:
Object *LayoutGadget_1; /* object pointers */ Object *LayoutGadget_2; Object *PageGadget; /* Instantiate the page gadget with a child layout object. */ PageGadget = IIntuition->NewObject(NULL, "page.gadget", PAGE_Add, LayoutGadget_1 = IIntuition->NewObject(LayoutClass, NULL, LAYOUT_Orientation, LAYOUT_ORIENT_VERT, TAG_DONE), TAG_DONE); /* Now instantiate another layout gadget and add the page structure created by the previous call as a child object. */ LayoutGadget_2 = IIntuition->NewObject(LayoutClass, NULL, LAYOUT_Orientation, LAYOUT_ORIENT_VERT, LAYOUT_AddChild, PageGadget, TAG_DONE); /* Do something and, ultimately, dispose of the parent object. Both children (PageGadget and LayoutGadget_1) will be disposed of automatically. */ IIntuition->DisposeObject(LayoutGadget_2);
(to be continued)
Context and Input/Output
Describe how input.task is involved, etc.