Difference between revisions of "Subclassing Application Class"

From MorphOS Library

(Code Inspection: Another paragraph.)
(Crosslinking++;)
 
(11 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
''Grzegorz Kraszewski''
 
''Grzegorz Kraszewski''
 +
----
 +
<small>This article in other languages: [[Klasy pochodne od klasy "Application"|Polish]]</small>
  
  
==Introduction==
+
Every MUI application is (or at least should be) an [[Event Driven Programming, Notifications|event driven]] one. This means the application provides a set of actions, which may be triggered with user activity (like using the mouse and keyboard). The straightforward way of implementing this set of actions is to implement it as a set of methods 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 a multi-document interface) may add actions to other classes, for example the ''Window'' class.
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:
 
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 used directly as [[Event Driven Programming, Notifications#Notifications in MUI|notification]] actions. It saves a programmer from using hook tricks or cluttering the main loop with lots of ''ReturnID'' values.
 
* Methods may be coupled directly with scripting language interface (formerly known as ARexx interface) commands.
 
* 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).
+
* Methods used in notifications are executed immediately in response to 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.  
+
* A notification triggering attribute value may be [[Event Driven Programming, Notifications#Reusing Triggering Value|passed directly]] to a method as its parameter.  
* Using methods improves code modularity.
+
* Using methods improves code modularity and object encapsulation. Functionality meant to be handled in the scope of an object is handled in its method, without the code spreading across the project.
  
 
+
In a well designed MUI program, all program actions and functionality are implemented as methods and the program internal state is stored as a set of attributes and internal application instance data fields. An example of such a program is discussed throughly in the [[MUI Subclassing Tutorial: SciMark2 Port]] article.
==The Application==
 
Many programming tutorials tend to bore readers with some useless examples. In this one a "real world" application will be ported to MorphOS and "MUI-fied". The application is [http://math.nist.gov/scimark2/index.html SciMark 2]. SciMark is yet another CPU/memory benchmark. It performs some typical scientific calculations like Fast Fourier Transform, matrix LU decomposition, sparse matrix multiplication and so on. The benchmark measures mainly CPU speed at floating point calculations, cache efficiency and memory speed. Being written in Java initially, it has been rewritten in C (and in fact in many other languages). The C source is available on the [http://math.nist.gov/scimark2/download_c.html project homepage].
 
 
 
The source uses only pure ANSI C standard, so it is easily compilable on MorphOS using provided ''Makefile''. One has just replace ''$CC = cc'' line to ''$CC = gcc'', to match the name of the MorphOS compiler. As a result, a typical shell-based application is obtained. Here are example results for a Pegasos 2 machine with G4 processor:
 
 
 
 
 
[[File: scimark_cli_noopt.png|center]]
 
 
 
 
 
Not very impressive in fact. This is because no optimizaton flags are passed to the compiler in the makefile. They can be added by inserting a line ''$CFLAGS = -O3'' below the ''$CC = gcc'' one. Let's also link with ''libnix'' (a statically linked unix environment emulation, see [[Installation_of_Software_Development_Kit_and_its_basic_usage#Standard_C_and_C.2B.2B_Libraries|Standard C and C++ Libraries]]) by adding ''-noixemul'' to ''CFLAGS'' and ''LDFLAGS''. After rebuilding the program and running it again the results are significantly improved (the program has been compiled with GCC 4.4.4 from the official SDK).
 
 
 
 
 
[[File: scimark_cli_opt.png|center]]
 
 
 
 
 
This shows how important is optimization of the code, especially computationally intensive one. Optimized code is '''more than 4 times faster'''!
 
 
 
 
 
==Code Inspection==
 
The original source code is well modularized. Five files: ''FFT.c'', ''LU.c'', ''MonteCarlo.c'', ''SOR.c'' and ''SparseCompRow.c'' implement the five of single benchmarks. Files ''array.c'' and ''Random.c'' contain auxiliary functions used in benchmarks. File ''Stopwatch.c'' implements time measurement. An important file ''kernel.c'' gathers all the above and provides five functions performing complete benchmarks with timing. Finally ''scimark2.c'' contains the ''main()'' function and implements the shell interface.
 
 
 
A planned MUI interface should allow to run every benchmark separately and run all of them. There is also ''-large'' option, which increases memory sizes of calculated problems, so they do not fit into the processor cache. A general rule of porting is that as few files as possible should be modified. The rule makes it easier to upgrade the port when a new version of original program is released. In the case of SciMark, only one file, ''scimark2.c'' has to be replaced. An advanced port may also replace ''Stopwatch.c'' with code using ''timer.device'' directly for improved time measurements accuracy, this is out of scope of this tutorial however.
 
 
 
A closer look at "scimark2.c" reveals that there is a ''Random'' object (a structure defined in "Random.h"), which is required for all the benchmarks. In the original code it is created with ''new_Random_seed()'' at the program start and disposed with ''delete_Random()'' at exit. The best place for it in the MUI-fied version is the instance data area of subclassed ''Application'' class. Then it can be created in ''OM_NEW()'' of the class and deleted in ''OM_DISPOSE()''. These two methods should be overridden then.
 
 
 
==GUI Design==
 
[[File:scimark_gui.png|left]] Of course there is no one and only proper GUI design for SciMark. A simple design, using a limited set of MUI classes is shown on the left. There are five buttons for individual benchmarks and one for running all of them. All these buttons are instances of the ''Text'' class. On the right there are gadgets for displaying benchmark results. These gadgets belong to ''Text'' class too, just having different attributes. The "Large Data" button, of ''Text'' class of course, is a toggle one. Surprisingly the status bar (displaying "Ready.") is not an instance of the ''Text'' class, but ''Gauge'' class. Then it will be able to display a progress bar when running all five tests. Spacing horizontal bars above the "All Benchmarks" button are instances of the ''Rectangle'' class. There are also three invisible objects of the ''Group'' class. The first is a vertical, main group, being the root object of the window. It contains two sub-groups. The upper one is the table group with two columns and contains all the benchmark buttons and result display gadgets. The lower group contains "Large Data" toggle button and the status bar.
 
 
 
The simplest way to start with GUI design is just to copy the "Hello World" [["Hello world!" in MUI|example]]. Then MUI objects may be added to ''build_gui()'' function. The [http://krashan.ppa.pl/morphzone_tutorials/scimark2_mui.c modified example] is ready to compile and run. It is not a complete program of course, just a GUI model without any functionality added.
 
 
 
A quick view into the ''build_gui()'' function reveals that it does not contain all the GUI code. Code for some subobjects is placed in functions called from the main ''MUI_NewObject()''. Splitting the GUI building function into many subfunctions has a few important advantages:
 
* Improved code readability and easier modifications. A single ''MUI_NewObject()'' call gets longer and longer quickly as the project evolves. Editing a large function spanning over a few screens is uncomfortable. Adding and removing GUI objects in such a function becomes nightmare even with indentation used consequently. On the other hand the function can have 10 or more indentation levels, which makes it hard to read as well.
 
* Code size reduction. Instead of writing very similar code multiple times, for example buttons with different labels, a subroutine may be called with label as an argument.
 
* Debugging. It happens sometimes that MUI refuses to create the application object because of some buggy tags or values passed. If the main ''MUI_NewObject()'' call is splitted into subfunctions, it is easy to isolate the buggy object by inserting some ''Printf()''-s in subfunctions.
 
 
 
====Creating Textfields====
 
 
 
The ''Text'' MUI class is used not only for creating static texts or labels (like in the [["Hello world!" in MUI|"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 [[Short BOOPSI Overview#Classes|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 [http://krashan.ppa.pl/morphzone_tutorials/scimark2_application.h class header file] and [http://krashan.ppa.pl/morphzone_tutorials/scimark2_application.c class code] can be written. The class still does nothing, just implements six empty methods and overrides ''OM_SET()'' for the attribute. In fact it is a boring template work and as such has been generated with [http://downloads.morphzone.org/find.php?find=chocolatecastle 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);
 

Latest revision as of 13:49, 27 January 2011

Grzegorz Kraszewski


This article in other languages: Polish


Every MUI application is (or at least should be) an event driven one. This means the application provides a set of actions, which may be triggered with user activity (like using the mouse and keyboard). The straightforward way of implementing this set of actions is to implement it as a set of methods 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 a multi-document interface) may add actions to other classes, for example the Window class.

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 lots 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 to 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 a method as its parameter.
  • Using methods improves code modularity and object encapsulation. Functionality meant to be handled in the scope of an object is handled in its method, without the code spreading across the project.

In a well designed MUI program, all program actions and functionality are implemented as methods and the program internal state is stored as a set of attributes and internal application instance data fields. An example of such a program is discussed throughly in the MUI Subclassing Tutorial: SciMark2 Port article.