B. Your first application

The first thing to do, of course, is download the Feelin package and install it. You can always get the latest version from the download page, and since Feelin is an open-source project, if you are curious or want to become a contributor, you can also download its sources.

To begin our introduction to Feelin, we'll start with the simplest program possible : an application with an empty window.

Although most of the current classes focus on GUI purpose, Feelin itself is not limited to that. Because Feelin is a general and open framework, it can be used in a variety of projects.

Please, also keep in mind that the preview pictures on this page (and others) are not builtin look. All UI objects, from the windows to the gadgets, are fully customizable. With Feelin, it's the end user who defines the look (and even the layout thanks to XML support) of its applications, not the developer.

B. Your first application

B.1. The example

preview
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/feelin.h>
#include <libraries/feelin.h>
struct FeelinBase *FeelinBase;
#ifdef __amigaos4__ struct FeelinIFace *IFeelin; #endif
int main(void) { if (F_FEELIN_OPEN) { FObject app = AppObject, Child, WindowObject, FA_Window_Open, TRUE, End, End;
if (app) { IFEELIN F_Do(app,FM_Application_Run);
IFEELIN F_DisposeObj(app); } else { IDOS_ Printf("Unable to create application\n"); }
F_FEELIN_CLOSE; } return 0; }

B.2. Step by step walkthrough

You can compile the above program with:

SAS/C using sc INCLUDEDIR Feelin:Includes base.c
VBCC using vc -IFeelin:Includes -lfeelin base.c -o base
GCC using gcc -IFeelin:Includes -lfeelin -noixemul base.c -o base

B.3. Notification

Includes

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/feelin.h>
#include <libraries/feelin.h>
struct FeelinBase *FeelinBase;

All programs will of course include <libraries/feelin.h> which declares the variables, functions, structures, etc. that will be used in your Feelin application.

#ifdef __amigaos4__
struct FeelinIFace *IFeelin;
#endif

This is for our AmigaOS4 friends, where library functions are called in a OOP style. Because of new style, the IFEELIN macro must be used before any call to the API. It does nothing on other Amiga systems (OS3/MOS/AROS), but will make your program easily portable to OS4. The same king of macro exists for most system libraries : IINTUITION, IGRAPHICS, ILAYERS, IUTILITY, IDOS_ (mind the underscore on this one !)...

Opening the library

int main(void)
{
    if (F_FEELIN_OPEN)
    {

The F_FEELIN_OPEN macro is used to open the feelin.library matching includes version.

Creating the application tree

        FObject app = AppObject,
            Child, WindowObject,
                FA_Window_Open, TRUE,
            End,
        End;

Feelin applications are built like trees, the root being the Application object. An application may have several children, subclasses of the Window class. Each window may have one root object, subclass of the Widget class. Usually, this root is an object from a Group subclass. Each Group subclass object may have several children, subclasses of the Widget class. In this example, the application has only one window children, which have no child.

AppObject, WindowObject, Child and End are macros. They are used to save some typing and make your code look nicer. For example, the AppObject macro replaces "F_NewObj(FC_Application", the Child macro replaces "FA_Child" and finally the End macro replaces "TAG_DONE)". An object generation macro such as AppObject is available for each class in the Feelin package.

MUI users might note the FA_Window_Open attribute in the initial taglist. Modifying this attribute usually results in closing or opening a window, when it's set to FALSE or TRUE respectively. Usually the window is opened or closed immediately when the attribute is modified, but if the application is asleep, which is the case until the FM_Application_Run method is invoked, the effect of the attribute is differed. Thus if we set this attribute to TRUE at object creation time, the window will only open once the FM_Application_Run method is invoked on the Application object.

Testing tree creation success

        if (app)
        {

To know if an application was successfully created, all we need to do is check its object pointer. If the creation of an object from the application tree failed, at any level, the whole tree is disposed (destroyed). When an application tree is disposed, parents dispose their children. Thus, all resources previously allocated are freed.

Launching the application

            F_Do(app,FM_Application_Run);

This line enters the application main processing loop. You'll see it in every Feelin application. When control reaches this point, the application is awaken, setuped and needed windows are opened. Then, the application is put to sleep again, waiting for events (such as buttons, keys press or window events), timers, or file IO notifications to occur. At this version of our program, the only way to quit the application is sending it a Ctrl-C signal.

Disposing the application tree

            F_DisposeObj(app);

Once control returns from the FM_Application_Run method, all we need to do is dispose the application. The application will dispose its children, parents will dispose their children, until every object in the application tree is disposed and all resources previously used are freed.

Closing the library

        }
        else
        {
            Printf("Unable to create application\n");
        }
F_FEELIN_CLOSE;

The F_FEELIN_CLOSE macro is used to close feelin.library, previously opened with the F_FEELIN_OPEN one.

Ending nicely

    }
    return 0;
}

Well, that's it ! We opened a window. But it's not very nice, because we need to terminate the application ourselves by sending it a Ctrl-C signal. Let's breathe some life into our application.

B.4. Hello world

Feelin is an event driven framework, which means the application sleeps in the FM_Application_Run method until an event occurs and control is passed to the appropriate function. Except for system events or user inputs, most of the time this passing of control is done using the idea of notification handlers.

Notification handlers

A notification handler can be set upon any attribute of an object, as long as the attribute is modifiable with the FM_Set method and the object's rootclass is the Object class. You create a notification handler by using the FM_Notify method and a notification statement, which consists of a source object, an attribute/value pair belonging to it, a target object a notification method, which is invoked with its message on the target object when a notify occurs.

Whenever the source object gets the given attribute set to the given value, the destination object is invoked with the notification method and its message. With FV_Notify_Always used as value, you can trigger the notification every time the attribute is modified, no matter its value. In that special case, you can include the value of the triggering attribute within the notification message using the FV_Notify_Value or FV_Notify_Toggle special values.

Bypassing notification handlers

Attributes can be modified without triggering any notification, by simply setting the FA_NoNotify attribute to TRUE within the taglist used to modify one or several attributes. This behavior is very useful when you modify yourself an attribute on which you had set a notification.

Removing a notification handler

You don't have to care about notification handlers because they are freed along with the object they belong to. In some particular cases, you may need to remove a notification handler before the object is disposed. Use the FM_UnNotify method for this purpose.

A real word example

As a real world example, let's say you want to shutdown your application when its main window is closed. First of all you need to get the pointer of your main window :

        ...
FObject app, win;
app = AppObject, Child, win = WindowObject, End,
...

The FA_Window_CloseRequest attribute of the window is set to TRUE when the user wants to close it, which happens if he releases its close button or presses FV_KEY_CLOSEWINDOW while the window is active. Thus, all we have to do to automatically shutdown the application when the user wants to close the main window, is simply use the following notification statement :

F_Do
(
    win, FM_Notify,
    FA_Window_CloseRequest,TRUE,
    app, FM_Application_Shutdown,0
);

That's all ! Complexer usage is possible, later we'll see how to provide a method message and use the special values FV_Notify_Always and FV_Notify_Value. Now that we now how to automatically shutdown an application, it's time to give some children to that empty window.

B.5. An upgraded Hello World

Feelin provides a builtin objects collection to quickly create commonly used objects such as buttons, sliders, gauges... Since most attributes are already defined within the collection, you can create objects by providing the minimum number of attributes. For example, instead of a "Button" class, the Button macro is provided to quickly create buttons through the F_MakeObj() function.

preview
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/feelin.h>
#include <libraries/feelin.h>
struct FeelinBase *FeelinBase;
int main(void) { if (F_FEELIN_OPEN) { FObject app,win;
app = AppObject, Child, win = WindowObject, FA_Window_Open, TRUE, FA_Window_Title, "Hello world !",
Child, Button("Hello world !"),
End, End;
if (app) { F_Do(win,FM_Notify, FA_Window_CloseRequest,TRUE, app,FM_Application_Shutdown,0);
F_Do(app,FM_Application_Run);
F_DisposeObj(app); } else { Printf("Unable to create application\n"); }
F_FEELIN_CLOSE; } return 0; }

B.6. The debug system

preview
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/feelin.h>
#include <libraries/feelin.h>
struct FeelinBase *FeelinBase;
struct FS_NOTIFY_BUTTON { uint32 Number, Pressed; };
F_HOOKM(void,code_notify_button,FS_NOTIFY_BUTTON) { F_Log(FV_LOG_USER, "button %ld %s", Msg->Number, Msg->Pressed ? "pressed" : "released"); }
int main(void) { if (F_OPEN_FEELIN) { FObject app,win,bt1,bt2;
app = AppObject, Child, win = WindowObject, FA_Window_Open, TRUE, FA_Window_Title, "Notify",
Child, HGroup, Child, bt1 = Button("Button _1"), Child, bt2 = Button("Button _2"), End, End, End;
if (app) { F_Do(win,FM_Notify, FA_Window_CloseRequest,TRUE, app,FM_Application_Shutdown,0); F_Do(bt1,FM_Notify, FA_Widget_Pressed,FV_Notify_Always, FV_Notify_Self,FM_CallHookEntry,3, code_notify_button,1,FV_Notify_Value); F_Do(bt2,FM_Notify, FA_Widget_Pressed,FV_Notify_Always, FV_Notify_Self,FM_CallHookEntry,3, code_notify_button,2,FV_Notify_Value);
F_Do(app,FM_Application_Run);
F_DisposeObj(app); } else { Printf("Unable to create application\n"); }
F_CLOSE_FEELIN; } return 0; }

B.7. Data types

Playing with the buttons "Button 1" and "Button 2" will log something like this:

[base] Text{10416684} @ Object.CallHookEntry> button 1 pressed
[base] Text{10416684} @ Object.CallHookEntry> button 1 released
[base] Text{104165C0} @ Object.CallHookEntry> button 2 pressed
[base] Text{104165C0} @ Object.CallHookEntry> button 2 released
[base] Text{10416684} @ Object.CallHookEntry> button 1 pressed
[base] Text{10416684} @ Object.CallHookEntry> button 1 released

Feelin features its own debug system. Each invocation is traced and precious information are collected. These information are used by the F_Log() function to add interesting things to your message.

Here for example, [base] is the name of the process or task executing the code. Text{10416684} is the class name and the address of the object. @ Object.CallHookEntry indicates which method was invoked on the object. Notice the @ Object which indicates that the message was logged from the Object class implementation of the method. Then follows your formatted message. Note well that the linefeed character is automatically added.

B.8. Application identity

There are a few things you probably noticed in the previous examples that need explaining. The uint32 that you see is typedef to unsigned long. This is done to get around that nasty dependency on the size of simple data types when doing calculations. The typedefs are very straightforward and intuitive. Currently, the following types are defined in <feelin/types.h> :

typedef char                                    int8;
typedef short                                   int16;
typedef long                                    int32;
typedef unsigned char uint8; typedef unsigned short uint16; typedef unsigned long uint32;
typedef float float32; typedef double float64;
typedef unsigned char bits8; typedef unsigned short bits16; typedef unsigned long bits32;

Although there is no difference between bits32 and uint32 (or bits16 and uint16), bits32 is generaly used instead of uint32 to indicate that a variable or a structure member is not a simple integer but rather a field of bits. Thus, bits32 is generaly used for flags.

Giving some identity to your application is very important. It enables a lot of user friendly things such as : ARexx support, Commodity support, objects attribute persistence (e.g. window coordinates, buttons states, strings contents...) as well as local preferences (relative to global ones).

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/feelin.h>
#include <libraries/feelin.h>
struct FeelinBase *FeelinBase;
struct FS_NOTIFY_BUTTON { uint32 Number, Pressed; };
F_HOOKM(void,code_notify_button,FS_NOTIFY_BUTTON) { F_Log(FV_LOG_USER, "button %ld %s", Msg->Number, Msg->Pressed ? "pressed" : "released"); }
int main(void) { if (F_OPEN_FEELIN) { FObject app,win,bt1,bt2;
app = AppObject, FA_Application_Title, "Base", FA_Application_Version, "$VER: Base 01.00 (2005/07/20)", FA_Application_Copyright, "©2000 - 2005, Olivier LAVIALE", FA_Application_Author, "Olivier LAVIALE - gofromiel@gofromiel.com", FA_Application_Description, "Getting started example", FA_Application_Base, "BASE",
Child, win = WindowObject, FA_ID, "windows.main", FA_Element_Persists, "left top width height", FA_Window_Open, TRUE, FA_Window_Title, "Notify", Child, HGroup, Child, bt1 = Button("Button _1"), Child, bt2 = Button("Button _2"), End, End, End;
if (app) { F_Do(win,FM_Notify, FA_Window_CloseRequest,TRUE, app,FM_Application_Shutdown,0); F_Do(bt1,FM_Notify, FA_Widget_Pressed,FV_Notify_Always, FV_Notify_Self,FM_CallHookEntry,3, code_notify_button,1,FV_Notify_Value); F_Do(bt2,FM_Notify, FA_Widget_Pressed,FV_Notify_Always, FV_Notify_Self,FM_CallHookEntry,3, code_notify_button,2,FV_Notify_Value);
F_Do(app,FM_Application_Run);
F_DisposeObj(app); } else { Printf("Unable to create application\n"); }
F_CLOSE_FEELIN; } return 0; }

Application title

FA_Application_Title, "Base",

The application title is used as broker name and is shown in the commodities Exchange program. It's also displayed in the Feelin's preference editor when editing local preferences. This attribute is required to create the broker.

Application version

FA_Application_Version, "$VER: Base 01.00 (2005/07/20)",

This is the version string of your application. The real part of the string (e.i. "Base 01.00 (2005/07/20)") is used as broker title and show in the commodities Exchange program. This attribute is required to create the broker.

Application copyright and author

FA_Application_Copyright, "©2000 - 2005, Olivier LAVIALE",
FA_Application_Author, "Olivier LAVIALE - gofromiel@gofromiel.com",

These two lines are optional. They provide further information about the application, which may be used in a about dialog window.

Application description

FA_Application_Description, "Getting started example",

This is a short description of your application, which is displayed in the commodities Exchange program.

Application base

FA_Application_Base, "BASE",

This attribute has several usage.

First it is used as the name of the application ARexx message port. For example, if your application base is "AMP", you can quit the application using ARexx as follows :

rx "address 'AMP' QUIT"

or hide it with :

rx "address 'AMP' HIDE"

The application base is also used as file name when importing and exporting objects data. If your application base is "AMP" objects data are imported from the file "ENV:Feelin/AMP.pdr". Note that only objects with the Id attribute different than NULL may import and export data.

Finally, the application base is used as preference file name to store local preferences. If your application base is "AMP", preferences are loaded (and stored) from the file "ENV:Feelin/AMP.css". Note that only applications with a base name can have local preferences.

Development