Copyright (c) Hyperion Entertainment and contributors.
Programming AmigaOS 4: GUI Toolkit ReAction
This article was adapted from Amiga Future magazine's series on developing for AmigaOS....
We mainly worked with shell programs or simple windows in the previous parts of this workshop. However, today's topic will be how to create a completely interactive GUI.
For this purpose we will use ReAction, which provides great flexibility for defined windows and controls that lay themselves out automatically. But before we dive into this subject, let us take a look at the past and see how interface development on the Amiga has changed over the years.
History
With Kickstart 1.x you had to create gadgets (the controls, which were back then - a button, a panel and a slide bar) directly as a structure and connect them with each other as well as calculate their positions. This procedure was very time consuming and a different font or another window size could sometimes cause significant trouble. Therefore, the gadtools.library, which was supplied with Kickstart 2.x, greatly simplified the creation of gadgets and menus. Additionally, a great number of new gadget types (e.g. ListView, Checkbox, Cycle) was launched. However, there was still a problem with the fixed positions. In this step the BOOPSI (Basic Object Oriented Programming System for Intuition) was also created. It offered an object-orientated approach, that could readily be used with Workbench 3.5 in the form of ReAction gadgets. This made it possible for windows to lay themselves out correctly automatically. That means that the gadgets automatically re-position themselves and their size is adjusted, if needed, when the window size is changed. The calculation of the minimized window size when opening a window is handled automatically by the system.
The end of Gadtools
By introducing ReAction all gadget types from the gadtools.library became superfluous. The same procedure is still used with AmigaOS4, but this time it is used to create menus. All the functions required to create and layout menus are now available in the window class and no longer need to be made automatically. In other words, gadtools.library no longer has this role to perform!
A side note: the MUI (Magic User Interface) is used as a second GUI library and it is provided with the Operating System since AmigaOS4. However, it remains an external toolkit. Its standard appearance has been made in the style of AmigaOS4 so it would not look like an 'alien element'. In principle, it applies the same approach as for ReAction, but it is programmed differently.
ReAction step by step
After that brief introduction it is now time for our first example, a window with a gadget and a menu. What used to take a long time, is now done in just a few steps. These necessary steps will be explained here in detail, because you can create big applications with them as well.
All ReAction objects (gadgets, window and Requester) are set up with IIntuition->NewObject(). The result is a generic 'Object' pointer. However, most of the old functions still expect a pointer on a gadget structure (or a window structure), so that you must decide whether you want to cast the return value immediately, or once you activate the respective functions. There is no standard notation in SDK at the moment. Right now, casting the result pointer is the best option. This way you can immediately see what it is about, in case you have selected an unfavourable name for the variable. You can create a button as follows:
struct Gadget *gb_ButtonGad; #define GAD_ID_Quit 2
if((gb_ButtonGad = (struct Gadget *) IIntuition->NewObject(IButton->BUTTON_GetClass(),NULL, GA_ID, GAD_ID_Quit, GA_RelVerify, TRUE, GA_Text, "_end", TAG_DONE)))
There is an example for various gadget classes at the end of this article. The GA_xxx tags are general and applicable to all kinds of gadgets. A specific gadget for a button would be, for instance, BUTTON_BevelStyle, which you can find under "gadgets/...". With the underscore in the button text the shortcut is defined, which can start the button without a mouse.
You can also use the same procedure to create a window, but this time you must use the WINDOW_xxx Defines from "classes/window.h". In addition, you can also use a great number of old WA tags.
Object *gb_WindowObj; if((gb_WindowObj = (Object *) IIntuition->NewObject(IWindow->WINDOW_GetClass(),NULL, WA_IDCMP, IDCMP_GADGETUP | IDCMP_CLOSEWINDOW | IDCMP_MENUPICK, WA_SizeGadget, TRUE, WA_DepthGadget, TRUE, WA_DragBar, TRUE, WA_CloseGadget, TRUE, WA_Activate, TRUE, WA_Title, "Example with reaction gadgets and window class", WINDOW_Position, WPOS_CENTERSCREEN,
A ReAction window requires implicitly a layout object, which automatically handles the size adjustment and distribution of the individual gadgets. In our example with only one gadget we need a central layout method. The layout object serves the horizontal and vertical arrangement of several gadgets within the window. This way, you can also realise very complex layouts by dividing them into horizontal and vertical groups.
struct Gadget *gb_MainLayout; WINDOW_ParentGroup, gb_MainLayout = (struct Gadget *) IIntuition->NewObject(ILayout->LAYOUT_GetClass(),NULL, LAYOUT_Orientation, LAYOUT_ORIENT_VERT, LAYOUT_SpaceOuter, TRUE, LAYOUT_SpaceInner, TRUE, LAYOUT_AddChild, gb_ButtonGad, TAG_DONE), TAG_DONE)))
As already mentioned, creating menus has become a very simple task in the meantime. All you need to do is create the NewMenu structure (from "libraries/gadtools.h") dating from gadtools times, which describes the individual menu points. This structure is given then when you create the window object. You no longer need to take care of deleting the menu structure; this happens automatically when the window object is released.
struct NewMenu gb_MenuDescribe[] = { { NM_TITLE, "Project", NULL, 0, 0, NULL }, { NM_ITEM, "Quit", NULL, 0, 0, NULL }, { NM_END, NULL, NULL, 0, 0, NULL } };
#define MENU_ID_Quit FULLMENUNUM(0,0,NOSUB)
IIntuition->NewObject(IWindow->WINDOW_GetClass(), WINDOW_NewMenu, gb_MenuDescribe,
Since AmigaOS4's release the ReAction gadgets have been included in the BubbleHelp. If you move the mouse cursor over such an element, a bubble with help text will be displayed soon afterwards. You need to do just a little bit of programming for this. Mainly, you must fill out the HintInfo structure from "classes/window.h":
struct HintInfo gb_HintInfo[] = { { GAD_ID_Help, -1, "activate/deactivate the BubbleHelp hints", 0 }, { GAD_ID_Quit, -1, "quit program", 0 }, { -1, -1, NULL, 0 } };
The fourth value is reserved for the future flags and a 0 is assigned to it at the moment. The structure must be indicated when you create the window object.
IIntuition->NewObject(IWindow->WINDOW_GetClass(), WINDOW_HintInfo, gb_HintInfo, WINDOW_GadgetHelp, TRUE,
With the WINDOW_GadgetHelp you can turn on/off the help. With 'WINDOW_GadgetHelp, FALSE' you can deactivate it, so it will not be displayed again. With TRUE you can re-activate it. You can change this tag during runtime. Other settings such as influencing the waiting time until the display, the display duration, the colour, etc. are still not possible, but users should be able to change them by using a global Prefs adjuster in the future.
IIntuition->SetAttrs(gb_WindowObj, WINDOW_GadgetHelp, FALSE, TAG_DONE);
We will add a checkbox to the sample program in order to put this possibility to use. We can turn on and off the help any time with it. In reality, you will set this option on a menu point or in the program settings, if there are any. A tooltype in the icon information is also an alternative.
struct Gadget *gb_CheckboxGad; #define GAD_ID_Help 1
if((gb_CheckboxGad = (struct Gadget *) IIntuition->NewObject(ICheckBox->CHECKBOX_GetClass(),NULL, GA_ID, GAD_ID_Help, GA_RelVerify, TRUE, GA_Text, "Bubble_Help active", TAG_DONE)))
WINDOW_ParentGroup, LAYOUT_AddChild, gb_CheckboxGad,
All GUI elements are now created, but they are still not visible. You should certainly check, whether the window pointer is present. If not, a gadget could probably not be created. To display the window you can use the RA_OpenWindow() macros from "reaction/reaction_macros.h", to close it RA_CloseWindow() and to edit news RA_HandleInput(). Since these are macros, you cannot use the typical interface notation. The news editing loop looks in practice as follows:
if((gb_Win = RA_OpenWindow(gb_WindowObj))) { ULONG winsig; IIntuition->GetAttr(WINDOW_SigMask,gb_WindowObj,&winsig);
BOOL run = TRUE; while(run) { ULONG result, code, val; const ULONG sigs = IExec->Wait(winsig | SIGBREAKF_CTRL_C);
while((result = RA_HandleInput(gb_WindowObj,&code)) != WMHI_LASTMSG) { switch(result & WMHI_CLASSMASK) { case WMHI_GADGETUP: switch(result & WMHI_GADGETMASK) { case GAD_ID_Help: IIntuition->GetAttr(CHECKBOX_Checked,gb_CheckboxGad,(ULONG*)&val); IIntuition->SetAttrs(gb_WindowObj, WINDOW_GadgetHelp, val, TAG_DONE); break;
case GAD_ID_Quit: run = FALSE; break; } break;
case WMHI_CLOSEWINDOW: run = FALSE; break;
case WMHI_MENUPICK: switch(result & WMHI_MENUMASK) { case MENU_ID_Quit: run = FALSE; break; } break; } }
if(sigs & SIGBREAKF_CTRL_C) run = FALSE; }
RA_CloseWindow(gb_WindowObj); }
You can see the various news types like press gadget (WMHI_GADGETUP), select menu (WMHI_MENUPICK) and the closing symbol for the window (WMHI_CLOSEWINDOW). But you can only get news, which has been defined explicitly as IDCMP_xxx Bits when creating the window.
To close the program, that is, to release all created objects all you need to do is activate IIntuition->DisposeObject(). It automatically makes sure that all elements included in the object are also released. If you specify the window pointer here, all included gadgets, menus and helptexts will be also deleted and can no longer be used!
IIntuition->DisposeObject(gb_WindowObj);
What is missing now are only the Includes and a main function. Then the program is complete and can be found as an example in the file "ReactionEx.c". Explaining comments about the individual areas are included in the source code too.
- Bonus
We can deal with programming with ReAction here only in theory. But for this purpose we have put "AllReactionGadgets.c" as a bonus on the ReaderCD. Here all available gadgets are used and you can see how they can be created and queried. The use of page gadgets is also shown. In conjunction with the Clicktab gadget you can also realise tabs, which always show another 'page' with gadgets. For instance, you can divide comprehensive setting windows into individual pages.
Reactor's approach, which you could use to 'click together' graphically a GUI under the Workbench 3.5 and 3.9, hasn't been developed any further. However, as shown in the previous example, you can basically copy the required GUI elements in your own sources with copy & paste.
Another bonus is the "ClickTab Test", which deals exclusively with the Clicktab gadget. As I already mentioned, you can use it to create tabs. The tabs can in the meantime be dynamically added and deleted again. Moreove, you can use both tags, so all tags have the same width and the tab labelling is not shortened.
Irrespectively of the programing side, the user can influence the appearance and partly the behaviour of gadgets and windows system-wide using the Prefs options. Angular or rounded edges, the thickness of the window frame, and also the complex colour scheme are all widely configurable. There are ready-made styles so everything works perfectly in the visual sense.
- Outlook
In the next part of this function you will get some practical help functions that are hidden in the utility.library. There are now some string functions in them, so that you can usually forego string.h.
[*** Kasten, vermutlich zweispaltig am Besten] The ReAction basis pointer
- Basis -
struct GadToolsIFace *IGadTools;
Basis pointer for the old Gadtools gadgets
struct WindowIFace *IWindow;
a window object
struct RequesterIFace *IRequester;
a requester object
struct LayoutIFace *ILayout; to put the gadgets in horizontal and vertical groups struct VirtualIFace *IVirtual;
Layout group is visible for larger surfaces
- Labels -
struct BevelIFace *IBevel; Frames to classify elements, optionally with labels struct BitMapIFace *IBitMap; displaying graphics, which are automatically loaded via DataType struct GlyphIFace *IGlyph; a group of predefined system symbols struct LabelIFace *ILabel; a field label, it must be assigned to another object struct PenMapIFace *IPenMap; comparable IBitMap, however, the data must be directly linked to the code
- Gadgets -
struct ButtonIFace *IButton; a normal button to press, it can also be turned on/off struct CheckBoxIFace *ICheckBox; a field to check off struct ChooserIFace *IChooser; a cycle gadget (retrievable as a PopUp or DropDown version) struct ClickTabIFace *IClickTab;
Dragbar for several pages
struct ColorWheelIFace *IColorWheel; colour wheel for colour selection struct DateBrowserIFace *IDateBrowser;
Months calendar
struct DrawListIFace *IDrawList;
to create freely scalable objects
struct FuelGaugeIFace *IFuelGauge; Fuel gauge display struct GetFileIFace *IGetFile; Display/selection of a file or a directory to load or store struct GetFontIFace *IGetFont; Display/selection of a font struct GetScreenModeIFace *IGetScreenMode;
Display/selection of a screenmode
struct IntegerIFace *IInteger; an integer field struct ListBrowserIFace *IListBrowser;
Listen object, it can include hierarchical groups and display elements
struct PaletteIFace *IPalette; Colour selection from a colour palette (max. 256 colour registers possible) struct PopupMenuIFace *IPopupMenu;
Display context-depending popup menus
struct RadioButtonIFace *IRadioButton; Selection groups, out of which only one can be selected struct ScrollerIFace *IScroller; Scroller with dynamic width struct SliderIFace *ISlider; Slider with set width struct SpaceIFace *ISpace; an "invisible" variables object struct SpeedBarIFace *ISpeedBar;
Speed bar
struct StringIFace *IString; a one-string text field struct TextClipIFace *ITextClip; simply text work in clipboard struct TextEditorIFace *ITextEditor; a several string text field (e.g. used in Notepad)
Authors
Written by Michael Christoph and Aleksandra Schmidt-Pendarovska
Copyright (c) 2013 Michael Christoph