Copyright (c) Hyperion Entertainment and contributors.
ReAction
Contents
Introduction
ReAction is a toolkit for GUI programming in AmigaOS. Originally a third-party product, it became part of the operating system in AmigaOS 3.5. The system, whose original tools have been rather limited and limiting, 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.
ReAction is based on Intuition's BOOPSI, an object-oriented philosophy. Understanding the basic concepts of BOOPSI is an important prerequisite for working with ReAction. Although this documentation tries to be as clear and helpful as possible, certain parts can make certain assumptions about the programmer's knowledge of BOOPSI, so make sure you have studied the BOOPSI article linked above.
There seems to be a certain degree of confusion as regards the actual 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.
Main features
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 hierarchical 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. On the other hand, ReAction is not compatible (= cannot exchange classes or combine GUI elements) with the MUI toolkit because the latter uses its own system of class management. Neither is ReAction compatible with the GadTools toolkit because it is not based on BOOPSI.
Class Overview
The following classes are currently installed as part of the ReAction toolkit, in the directory “SYS:Classes” on your system disk:
class | public name (ClassID) | function / GUI element type |
---|---|---|
ARexx | arexx.class | Simplifies the creation and usage of ARexx ports in your programs. |
PopUpMenu | popupmenu.class | Provides context (local) menus. |
Requester | requester.class | Greatly extends the original concept of Intuition requesters, providing several requester types with many features. |
Window | window.class | A powerful class to create windows with advanced features such as iconification, the AppWindow facility, or an automatic layouting system. |
The following Gadget Class sub-classes are currently installed as part of the ReAction toolkit, in the directory “SYS:Classes/Gadgets” on your system disk (the parent, Gadget Class, is part of Intuition):
class | public name (ClassID) | function / GUI element type |
---|---|---|
Button | button.gadget | |
CheckBox | checkbox.gadget | |
Chooser | chooser.gadget | |
ClickTab | clicktab.gadget | |
DateBrowser | datebrowser.gadget | |
FuelGauge | fuelgauge.gadget | |
GetColor | getcolor.gadget | |
GetFile | getfile.gadget | |
GetFont | getfont.gadget | |
GetScreenMode | getscreenmode.gadget | |
Integer | integer.gadget | |
Layout | layout.gadget | |
ListBrowser | listbrowser.gadget | |
Page | page.gadget | |
RadioButton | radiobutton.gadget | |
Scroller | scroller.gadget | |
SketchBoard | sketchboard.gadget | |
Slider | slider.gadget | |
Space | space.gadget | |
SpeedBar | speedbar.gadget | |
String | string.gadget | |
TextEditor | texteditor.gadget | |
Virtual | virtual.gadget |
The following Image Class sub-classes are currently installed as part of the ReAction toolkit, in the directory “SYS:Classes/Images” on your system disk (the parent, Image Class, is part of Intuition):
class | public name (ClassID) | function / GUI element type |
---|---|---|
Bevel | bevel.image | |
BitMap | bitmap.image | |
DrawList | drawlist.image | |
Filler | filler.image | |
Glyph | glyph.image | |
Label | label.image | |
PenMap | penmap.image | |
Table | table.image |
Attributes and Methods
BOOPSI objects (as defined by their respective classes) are described by attributes and manipulated through methods.
Attributes
An attribute is a particular property or feature of an object. While some classes implement attributes that are specific (and only relevant) to themselves, there are certain attributes that are common for a particular group of classes, eg. gadgets. In the AmigaOS implementation, an object attribute takes the form of a tag, which is a name/value pair. As most classes support multiple attributes, complete tag lists can be used to describe an object:
GA_Text, "Don’t ever dare to click on me!", GA_ReadOnly, TRUE, BUTTON_Justification, BCJ_LEFT, BUTTON_SoftStyle, FSF_ITALIC, TAG_END
The tag list above describes a hypothetical Button Gadget object. The first two tags are applicable to gadget classes in general (“GA” standing for “Gadget Attribute”). They say that the gadget should display the text “Don’t ever dare to click on me!” and that it should be rendered as a non-selectable (read-only) GUI element. The other two tags are specific to the ReAction Button Gadget. They say, respectively, that the text in the gadget should be left-aligned and rendered in italics. TAG_END indicates the end of the tag list.
Methods
A method is a function that does something to the object: creates it, destroys it, changes its properties, etc. While you can call – or as we say, invoke – BOOPSI methods directly using Intuition functions IDoMethod() and DoGadgetMethod(), the most common methods are hidden from the programmer and take the form of “normal” functions:
method | function(s) invoking this method | meaning |
---|---|---|
OM_NEW | NewObject() | creates a new object instance |
OM_DISPOSE | DisposeObject() | destroys the object and frees all associated resources |
OM_SET | SetAttrs(), SetGadgetAttrs() | sets or changes object properties |
OM_GET | GetAttr(), GetAttrs() | query about a particular object property or multiple properties |
The methods in the table above are applicable to all classes because they are defined at the highest (“rootclass”) level in the BOOPSI class hierarchy. This is why they are also referred to as rootclass methods.
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 the OM_NEW method, which every BOOPSI class must implement by specification and which creates an object instance.
NewObject() takes three arguments as input:
- a class pointer, as obtained from OpenClass();
- a class public name (also called ClassID), which is a text string;
- a tag list describing object properties (attributes).
The first two arguments are mutually exclusive: one of them will always be NULL. You can either instantiate the object from the class pointer, or from the public name (provided that 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_END); /* Instantiate the page gadget using the public class name. */ PageGadget = IIntuition->NewObject(NULL, "page.gadget", TAG_END); /* 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_END), TAG_END); /* 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_END); /* 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.