Subclassing Application Class
From MorphOS Library
Grzegorz Kraszewski
Contents
Introduction
Every MUI application is (or at least should be) an event driven one. It means the application provides a set of actions, which may be triggered with user activity (like using mouse and keyboard). The straightforward way of implementing this set of actions is to implement it as a set of metods added to some MUI object. For simple programs, the best candidate for adding such methods is the master Application object. More complex programs (for example ones using multi-document interface) may add actions to other classes, for example Window one.
Why methods? Implementing actions as methods has many advantages:
- Methods may be used directly as notification actions. It saves a programmer from using hook tricks or cluttering the main loop with teens of ReturnID values.
- Methods may be coupled directly with scripting language interface (formerly known as ARexx interface) commands.
- Methods used in notifications are executed immediately in response of user actions. No delay is introduced by the main loop (especially if it is not empty).
- A notification triggering attribute value may be passed directly to method, as its parameter.
- Using methods improves code modularity.
Creating Textfields
The Text MUI class is used not only for creating static texts or labels (like in the "Hello World!" example), but also framed readonly text gadgets and textual buttons. All these object differ only in attributes. A read-only framed text field has a specific background and frame:
Object* create_resdisplay(void) { Object *obj; obj = MUI_NewObject(MUIC_Text, MUIA_Frame, MUIV_Frame_Text, MUIA_Background, MUII_TextBack, TAG_END); return obj; }
Creating Buttons
As said above, a text button is an instance of the Text class too. It has more attributes than a plain label however. MUIA_Text_PreParse attribute centers the text in the object using a control code of the MUI text engine. After setting a frame and a background, a proper font is also specified with MUIA_Font. Forgetting this attribute is one of common interface design errors. Next four attributes are related to user input. MUIA_InputMode attribute defines type of button (push or toggle one). MUIA_CycleChain makes the button reachable with keyboard. Incomplete keyboard support for GUI is another common error and should be avoided. All gadgets receiving user input should have MUIA_CycleChain set. Most important gadgets should be also reachable with single keys. MUIA_ControlChar sets a key, which activates the gadget. For textual gadgets a letter of this key should be also underlined with MUIA_Text_HiChar. It implies that the character used should be present in the gadget label. Both these attributes should be set to small letters. MUIA_ControlChar set to a capital letter will force user to press SHIFT. MUIA_Text_HiChar is case insensitive. Digits can be also used for control characters, if button labels contain them. Using other characters is strongly discouraged.
The function is called six times in the code, and creates six pushbuttons. The toggle button "Large Data" is created by very similar create_datasize_button() function, which differs in MUIA_InputMode value. As it is used once only, the button label and control character are hardcoded inside the function and not passed as arguments.
MUIA_HorizWeight and MUIA_Text_SetMax attributes are used for layout control. They are described in the autodoc of Area and Text class respectively.
Object* create_button(char *label, char control) { Object *obj; obj = MUI_NewObject(MUIC_Text, MUIA_Text_Contents, (ULONG)label, MUIA_Text_PreParse, "\33c", MUIA_Frame, MUIV_Frame_Button, MUIA_Background, MUII_ButtonBack, MUIA_Font, MUIV_Font_Button, MUIA_InputMode, MUIV_InputMode_RelVerify, MUIA_Text_HiChar, control, MUIA_ControlChar, control, MUIA_CycleChain, TRUE, MUIA_HorizWeight, 1, TAG_END); return obj; }
Creating Spacing Bars
Horizontal and vertical bars may be used for visual separation of gadgets. They are created using Rectangle class. In a basic case the class creates just empty rectangles and is used for layout control. MUIA_Rectangle_HBar attribute adds a horizontal bar. The object has also height fixed, to avoid its vertical scaling. Bars and grouping frames should be used sparingly.
Object* create_hbar(void) { Object *obj; obj = MUI_NewObject(MUIC_Rectangle, MUIA_Rectangle_HBar, TRUE, MUIA_FixHeight, 2, TAG_END); return obj; }
Creating Gauge
A Gauge object creates a progress bar and an optional text on it. Gauges may be vertical or horizontal, but only horizontal ones can display text. Frame, background and font are specified as usual. An initial text causes the gauge object to have fixed height.
Object* create_statusbar(void) { Object *obj; obj = MUI_NewObject(MUIC_Gauge, MUIA_Frame, MUIV_Frame_Gauge, MUIA_Font, MUIV_Font_Gauge, MUIA_Background, MUII_GaugeEmpty, MUIA_Gauge_Horiz, TRUE, MUIA_Gauge_InfoText, (ULONG)"Ready.", TAG_END); return obj; }
Locating Objects in the Object Tree
After the complete object tree is created, there is no access to any object except Application and Window, as pointers to them are kept in global variables. A way to access other objects (in fact all of them except of groups) is needed. There are a few ways to do this:
- Adding more global variables. This is the simplest way and may work well in such a simple project. The disadvantage is it breaks object oriented design principles (like data encapsulation) and creates a mess when number of global variables reaches 50, 100 or more.
- Store pointers in fields of some subclass instance data (for example Application one). A good idea, but a bit tedious to implement. Object's instance data area does not exist until the object is fully created and the Application object is created as the last one. Then pointers to subobjects have to be stored in some temporary variables.
- Use MUIA_UserData attribute and MUIM_FindUData() method to find objects dynamically. This is the best solution when objects are accessed rarely (for example once, just to set notifications). For frequently accessed objects (let's say several times a second) it may be combined with caching objects' pointers in an instance data of some subclassed object.
The last approach works as follows: every object to be searched has the MUIA_UserData attribute set to some predefined unique value. Then at any time the object may be found by this value using MUIM_FindUData() method on a direct or indirect parent objects (it may be the Window or Application object).
#define OBJ_BUTTON_FFT 36 /* Somewhere in the constructor of the button */ MUIA_UserData, OBJ_BUTTON_FFT, /* ... */ /* Let's get the pointer to FFT button now */ Object *fft_button; fft_button = (Object*)DoMethod(Application, MUIM_FindUData, OBJ_BUTTON_FFT);
This operation is so common, that it is usually encapsulated in a macro:
#define findobj(id, parent) (Object*)DoMethod(parent, MUIM_FindUData, id) fft_button = findobj(OBJ_BUTTON_FFT, Application);
Methods and Attributes
The SciMark GUI just designed, defines six actions of the application. There are five actions of running individual benchmarks and the sixth one of running all the tests and calculate the global result. Actions will be directly mapped to an Application subclass methods. There is also one attribute connected with "Large Data" button, it determines sizes of problems solved by benchmarks. Methods do not need any parameters, so there is no need to define method messages. An attribute may be applicable at init time (in the object constructor), may be also settable (needs OM_SET() method overriding) and gettable (needs OM_GET() method overriding). Our new attribute, named APPA_LargeData in the code only needs to be settable. In the constructor it can be implicitly set to FALSE, as the button is switched off initially. Gettability is not needed, because this attribute will be used only inside the Application subclass.
It is recommended that every subclass in the application is placed in a separate source file. It helps to keep code modularity and also allows for hiding class private data. This requires writing a makefile, but one is needed anyway, as the original SciMark code consists of multiple files. Implementing the design directions discussed above a class header file and class code can be written. The class still does nothing, just implements six empty methods and overrides OM_SET(), OM_NEW() and OM_DISPOSE(). In fact it is a boring template work and as such it has been generated with ChocolateCastle template generator. Unfortunately ChocolateCastle is still beta, so files had to be tweaked manually after generation.
The next step in the application design is to connect methods and attributes with GUI elements using notifications. Notifications must be of course created after both source and target object are created. In the SciMark code they are just set up after executing build_gui(). All the six action buttons have very similar notifications, so only one is shown here:
DoMethod(findobj(OBJ_BUTTON_FFT, App), MUIM_Notify, MUIA_Pressed, FALSE, App, 1, APPM_FastFourierTransform);
"Large Data" button has a notification setting the corresponding attribute:
DoMethod(findobj(OBJ_BUTTON_LDATA, App), MUIM_Notify, MUIA_Selected, MUIV_EveryTime, App, 3, MUIM_Set, APPA_LargeData, MUIV_TriggerValue);
Overriding the Constructor
The first thing do be done in the constructor is calling the superclass method. There is an exception when an object created can have children objects. These objects are then created before calling superclass, usually with convenient DoSuperNew() call. When the object is ready, its instance data area is created and may be accessed.
Overriding the Destructor
The destructor should just free all resources allocated by the constructor, then call the superclass method.
IPTR ApplicationDispose(Class *cl, Object *obj, Msg msg) { struct ApplicationData *d = INST_DATA(cl, obj); if (d->R) Random_delete(d->R); return DoSuperMethodA(cl, obj, msg); }
The only resource to free is the Random structure.