6. Application Development

6.1 Introduction

The following code examples are used in this chapter:

The maemo platform's basic starting point for graphical user interface programming is the Hildon[44], an application framework comprising of a lightweight desktop, a set of widgets optimized for handheld devices, a set of theming tools and other complementary libraries and applications.

Hildon is based on GNOME[29] technologies to a great extent. Compared to a GNOME desktop from a user interface point of view, Hildon is designed to provide a new desktop for mobile embedded devices. Therefore it for example uses a lighter window manager called Matchbox[76].

6.2 Typical Maemo GUI Application

The following shows the components making up a typical GUI application developed for maemo (starting from the bottom):

There are some caveats related to API changes between major GTK+ versions, which will be mentioned in the text, so existing codes should not be copy-pasted blindly.

6.3 Hildon Desktop

The end user's desktop experience on Nokia Internet Tablets is provided by the Hildon Desktop that is based on Hildon. The latter provides several libraries to interface with the desktop environment UI elements and services.

Hildon framework's main UI elements are separated to four categories and all of them can be extended with plug-ins:

GUI applications in maemo usually have one or more HildonWindows, which are the top-level application windows. They can include a standardized menubar and a toolbar.

The Hildon framework also includes other auxiliary widgets and services specialized for the Hildon environment. Examples of these are the Hildon-FM library for file system dialogs and widgets, HildonBanner for displaying notifications to the user and HildonWizardDialog for creating wizard user interfaces. For information about Hildon's widgets, see Maemo API References[57].

Another important element of the Hildon framework is the Hildon Input Method API, which is an interface for creating new user input systems in addition to the included virtual keyboard and handwriting recognition.

6.4 Writing Maemo GUI Applications

6.4.1 Overview of Maemo GUI Applications

Hildon is a graphical user interface designed for small mobile devices. This section aims to assist developers in getting started with the development of Hildon-compatible applications.

Most GTK+ widgets and methods work unaltered in the Hildon environment, but to make applications fit in the maemo GUI environment, some source code changes for plain GTK+ applications are required.

The following sections introduce the most important widgets in Hildon; all GTK+ widgets can be used in addition of these. For more information, see the GTK+ Reference Manual at [41].

6.4.2 Basic Hildon Layouts

This section shows basic layout modes of the Hildon user interface. Applications can use all of these and switch dynamically between different views. In all the views, application area can, of course, be shared in any possible way using GTK+ containers (e.g. GtkHBox, GtkVBox and GtkTable).

6.4.2.1 Normal View

The figures below describe the basic layout of the Hildon user interface in the normal view. This view consists of the Task Navigator area, statusbar/titlebar area, application area and three areas of inactive skin graphics.

Image hildon_normal_layout

6.4.2.2 Normal View with Toolbar

This basic layout contains all types of functional areas. The layout is basically the same as the Normal View, but there is a toolbar area at the bottom, replacing the inactive skin graphic area. The toolbar area width is 720 pixels and the height varies depending on the toolbar type:

Image hildon_toolbar_layout

6.4.2.3 Full Screen View

In this view, the whole screen (800x480 pixels) is reserved for the application area. The full screen mode can be activated and deactivated by a hardware button, or in the application code. All applications should provide full screen mode to enable maximized screen usage when needed.

Image hildon_fullscreen_layout

6.4.2.4 Full Screen View with Toolbar

This is a variation of the full screen view. There is a toolbar area at the bottom of the screen, reducing the space available for the application area. The toolbar can be visible in both normal and full screen modes, so it should be made scalable.

Image hildon_fullscreen_toolbar_layout

6.4.3 Windows

6.4.3.1 HildonProgram

#include <hildon/hildon-program.h>
#include <gtk/gtkmain.h>

A HildonProgram is the base of any Hildon application. It is inherited from GObject, and represents an application running in the Hildon framework. The HildonProgram tracks the top-most status of an application, and informs the Task Navigator when the application can be hibernated. It also provides tools to get/set menus and toolbars common for all application windows.

The HildonProgram widget is created by calling the function
hildon_program_get_instance(), which does not take any parameters, and returns the newly-created HildonProgram. For this tutorial, the following API functions are of interest:

HildonProgram *hildon_program_get_instance (void)

- Creates new HildonProgram widget.



void hildon_program_add_window (HildonProgram *self, 
                                HildonWindow *window)

- Adds HildonWindow to HildonProgram.



hildon_program_set_can_hibernate (HildonProgram *self, 
                                  gboolean killable)

- Informs Task Navigator that it can hibernate the HildonProgram.



void hildon_program_set_common_menu (HildonProgram *self, 
                                     GtkMenu *menu)

- Sets common menu for all application windows.



void hildon_program_set_common_toolbar (HildonProgram *self, 
                                        GtkToolbar *toolbar)

- Sets common toolbar for all application windows.



This is an example (example_hildonprogram.c) application, which creates new HildonProgram and HildonWindow. It sets a title for the application and creates a sample label.

int main(int argc, char *argv[])
{
    /* Create needed variables */
    HildonProgram *program;
    HildonWindow *window;
    /* Initialize the GTK. */
    gtk_init(&argc, &argv);
    /* Create the Hildon program and setup the title */
    program = HILDON_PROGRAM(hildon_program_get_instance());
    g_set_application_name("App Title");
    /* Create HildonWindow and set it to HildonProgram */
    window = HILDON_WINDOW(hildon_window_new());
    hildon_program_add_window(program, window);
    /* Add example label to window */
    gtk_container_add(GTK_CONTAINER(window),
                      GTK_WIDGET(gtk_label_new("HildonProgram Example")));
    /* Begin the main application */
    gtk_widget_show_all(GTK_WIDGET(window));
    /* Connect signal to X in the upper corner */
    g_signal_connect(G_OBJECT(window), "delete_event",
    G_CALLBACK(gtk_main_quit), NULL);
    gtk_main();
    /* Exit */
    return 0;
}

To compile the program, type


$ gcc -o example_hildonprogram example_hildonprogram.c `pkg-config gtk+-2.0 hildon-1 
--cflags --libs` -Wall


To run this sample from inside Scratchbox, start the Xephyr server and


$ run-standalone.sh ./example_hildonprogram


And you will be able to see

Image HildonProgram_example

6.4.3.2 HildonWindow

#include <hildon/hildon-window.h>

The HildonWindow is inherited from the GtkWindow. It represents a top-level window of an application running in the Hildon framework. It provides facilities to manage window menus and toolbars. GtkWindow API applies to it as well (gtk_window_fullscreen, gtk_window_set_urgency_hint, ...).

Each HildonWindow can have its own menu and toolbars. Applications can also have common menu and toolbar, shared between all application windows. To get more information, check HildonProgram, Menus and Toolbars sections.

The following HildonWindow functions are the most important ones:

GtkWidget *hildon_window_new (void)

- Creates a new HildonWindow widget.



void hildon_window_set_menu (HildonWindow *self, 
                             GtkMenu *menu)

- Sets a menu for the HildonWindow.



void hildon_window_add_toolbar (HildonWindow *self, 
                                GtkToolbar *toolbar)

- Adds a toolbar for the HildonWindow.



See the previous section for a sample code.

6.4.4 Menus

#include <hildon/hildon-program.h>
#include <gtk/gtk.h>

The menus in maemo use GTK+ menu functions, with the exception that the menu is attached to the HildonProgram/Window, and appears in the title bar. A GtkMenu can be created and attached to the HildonProgram to be used as a common menu for all HildonWindows that do not have their own menu. Another way is to use a GtkMenu in a HildonWindow. This way, every application window can have different menu.

GTK+ menus can be made both manually and using GtkUIManager. The GtkUIManager is a new action-based method to create menus and toolbars using an XML description. This tutorial introduces only manual creation. More about GtkUIManager can be read at [42].

The following example (example_menu.c) creates a menu with a submenu. This submenu contains radio menu items and a check menu item, all of which can be toggled. The last item in the menu (Close) is attached to a callback function, so that the application can be closed also from the menu. The starting point here is the function creating this menu. In this example, the menu is attached to the one and only HildonWindow.

/* Create the menu items needed for the main view */
static void create_menu(HildonWindow * main_window) 
{
    /* Create needed variables */
    GtkWidget *main_menu;
    GtkWidget *menu_others;
    GtkWidget *item_others;
    GtkWidget *item_radio1;
    GtkWidget *item_radio2;
    GtkWidget *item_check;
    GtkWidget *item_close;
    GtkWidget *item_separator;
    /* Create new main menu */
    main_menu = gtk_menu_new();
    /* Create new submenu for "Others" */
    menu_others = gtk_menu_new();
    /* Create menu items */
    item_others = gtk_menu_item_new_with_label("Others");
    item_radio1 = gtk_radio_menu_item_new_with_label(NULL, "Radio1");
    item_radio2 =
        gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM
                                                       (item_radio1),
                                                       "Radio2");
    item_check = gtk_check_menu_item_new_with_label("Check");
    item_close = gtk_menu_item_new_with_label("Close");
    item_separator = gtk_separator_menu_item_new();
    /* Add menu items to right menus */
    gtk_menu_append(main_menu, item_others);
    gtk_menu_append(menu_others, item_radio1);
    gtk_menu_append(menu_others, item_radio2);
    gtk_menu_append(menu_others, item_separator);
    gtk_menu_append(menu_others, item_check);
    gtk_menu_append(main_menu, item_close);
    /* Add others submenu to the "Others" item */
    hildon_window_set_menu(HILDON_WINDOW(main_window), GTK_MENU(main_menu));
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(item_others), menu_others);
    /* Attach the callback functions to the activate signal */
    g_signal_connect(G_OBJECT(item_close), "activate",
                     GTK_SIGNAL_FUNC(item_close_cb), NULL);
    /* Make all menu widgets visible */
    gtk_widget_show_all(GTK_WIDGET(main_menu));
}

The menu entry "Close" is attached with the function "g_signal_connect" to the callback function "item_close_cb". This function, which is presented next, ends the application by executing "gtk_main_quit()".

/* Callback for "Close" menu entry */
void item_close_cb()
{
    g_print("Closing application...\n");
    gtk_main_quit();
}

The main function in this application is similar to others presented earlier. The only new thing here is executing the function "create_menu()" with HildonWindow as its parameter. This function creates the menu and attaches it to the window.

/* Main application */
int main(int argc, char *argv[])
{
    /* Create needed variables */
    HildonProgram *program;
    HildonWindow *window;
    /* Initialize the GTK. */
    gtk_init(&argc, &argv);
    /* Create the hildon program and setup the title */
    program = HILDON_PROGRAM(hildon_program_get_instance());
    g_set_application_name("Menu Example");
    /* Create HildonWindow and set it to HildonProgram */
    window = HILDON_WINDOW(hildon_window_new());
    hildon_program_add_window(program, window);
    /* Add example label to HildonWindow */
    gtk_container_add(GTK_CONTAINER(window),
                      gtk_label_new("Menu Example"));
    /* Create menu for HildonWindow */
    create_menu(window);
    /* Connect signal to X in the upper corner */
    g_signal_connect(G_OBJECT(window), "delete_event",
      G_CALLBACK(item_close_cb), NULL);
    /* Begin the main application */
    gtk_widget_show_all(GTK_WIDGET(window));
    gtk_main();
    /* Exit */
    return 0;
}

To compile and run, start Xephyr server and


$ gcc -o example_menu example_menu.c `pkg-config gtk+-2.0 hildon-1 --cflags --libs` -Wall
$ run-standalone.sh ./example_menu


Screenshot of the application is presented below, with the submenu opened.

Image example_menu

As the maemo menus are GTK+ menus, they are not explained here in more detail. For full API documentation regarding GTK+ menu widgets, see [40].

6.4.5 Toolbars

#include <hildon/hildon-program.h>
#include <gtk/gtk.h>

To create an application that has a toolbar, create a normal HildonWindow and then a normal GtkToolbar. The toolbar is used as any GtkToolbar is used, and it can contain normal GtkToolItems, like:

The main difference compared to a normal GTK+ usage is that toolbars can be common to all HildonWindows in an application, or they can be used only in a particular HildonWindow.

The function below creates a toolbar and adds it to the HildonWindow. In this example (example_toolbar.c), the defined function is called in the main function in the same way as in the previous menu example. The callback function for the 'Close' button (tb_close_cb) is similar to the earlier example, also.

/* Create the toolbar needed for the main view */
static void create_toolbar(HildonWindow * main_window)
{
    /* Create needed variables */
    GtkWidget *main_toolbar;
    GtkToolItem *tb_new;
    GtkToolItem *tb_open;
    GtkToolItem *tb_save;
    GtkToolItem *tb_close;
    GtkToolItem *tb_separator;
    GtkToolItem *tb_comboitem;
    GtkComboBox *tb_combo;
    /* Create toolbar */
    main_toolbar = gtk_toolbar_new();
    /* Create toolbar button items */
    tb_new = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
    tb_open = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN);
    tb_save = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE);
    tb_close = gtk_tool_button_new_from_stock(GTK_STOCK_CLOSE);
    /* Create toolbar combobox item */
    tb_comboitem = gtk_tool_item_new();
    tb_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
    gtk_combo_box_append_text(tb_combo, "Entry 1");
    gtk_combo_box_append_text(tb_combo, "Entry 2");
    gtk_combo_box_append_text(tb_combo, "Entry 3");
    /* Select second item as default */
    gtk_combo_box_set_active(GTK_COMBO_BOX(tb_combo), 1);
    /* Make combobox to use all available toolbar space */
    gtk_tool_item_set_expand(tb_comboitem, TRUE);
    /* Add combobox inside toolitem */
    gtk_container_add(GTK_CONTAINER(tb_comboitem), GTK_WIDGET(tb_combo));
    /* Create separator */
    tb_separator = gtk_separator_tool_item_new();
    /* Add all items to toolbar */
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_new, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_separator, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_open, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_save, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_comboitem, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_close, -1);
    /* Add signal lister to "Close" button */
    g_signal_connect(G_OBJECT(tb_close), "clicked",
                     G_CALLBACK(tb_close_cb), NULL);
    /* Add toolbar HildonWindow */
    hildon_window_add_toolbar(main_window, GTK_TOOLBAR(main_toolbar));
}

The screenshot presents the created toolbar with several buttons and combobox.

To compile and run, start Xephyr server and


$ gcc -o example_toolbar example_toolbar.c `pkg-config gtk+-2.0 hildon-1 --cflags --libs` 
-Wall
$ run-standalone.sh ./example_toolbar
Image example_hildontoolbar

6.4.6 HildonFindToolbar

#include <hildon/hildon-program.h>
#include <hildon/hildon-find-toolbar.h>
#include <gtk/gtk.h>

A HildonFindToolbar is a special toolbar for a find feature. Maemo applications may have several toolbars, which are then attached on top of each other. The Find toolbar is generally placed on the top of the main toolbar, as in this example.

Now a bit more complete application (example_findtoolbar.c), for which the Find toolbar needs to be opened and hidden. A structure is created to contain the main UI widget pointers. This is the way a real maemo application should be built to make accessing generated widgets easier. The code is reviewed here piece by piece, at first the data structure, holding important data needed to be accessed by other functions.

/* Application UI data struct */
typedef struct _AppData AppData;
struct _AppData {
    /* View items */
    HildonProgram *program;
    HildonWindow *window;
    /* Toolbar */
    GtkWidget *main_toolbar;
    /* Find toolbar */
    HildonFindToolbar *find_toolbar;
    /* Is Find toolbar visible or not */
    gboolean find_visible;
    /* Result label */
    GtkWidget *label;
};

The next part presents all the callback functions required: "Close" and "Find" for the main toolbar, and "Close" and "Search" for the Find toolbar. The actual search is not implemented, but it should be included in the find_tb_search() function.

/* Callback for "Close" toolbar button */
void tb_close_cb(GtkToolButton * widget, AppData * view)
{
    g_print("Closing application...\n");
    gtk_main_quit();
}

/* Callback for "Find" toolbar button */
void tb_find_cb(GtkToolButton * widget, AppData * view)
{
    /* Show or hide find toolbar */
    if (view->find_visible) {
        gtk_widget_hide_all(GTK_WIDGET(view->find_toolbar));
        view->find_visible = FALSE;
    } else {
        gtk_widget_show_all(GTK_WIDGET(view->find_toolbar));
        view->find_visible = TRUE;
    }
}

/* Callback for "Close" find toolbar button */
void find_tb_close(GtkWidget * widget, AppData * view)
{
    gtk_widget_hide_all(GTK_WIDGET(view->find_toolbar));
    view->find_visible = FALSE;
}

/* Callback for "Search" find toolbar button */
void find_tb_search(GtkWidget * widget, AppData * view)
{
    gchar *find_text = NULL;
    gchar *label_text = NULL;
    
    g_object_get(G_OBJECT(widget), "prefix", &find_text, NULL);
    /* Implement the searching here... */
    label_text = g_strdup_printf ("HildonFindToolbar Example.\n\nSearch for:\n\n%s.",  find_text);
    gtk_label_set_text (GTK_LABEL (view->label), label_text);
    g_free(label_text);
            
    g_print("Search for: %s\n", find_text);
}

Next, the HildonFindToolbar is created in a separate function, and connected to the callback listeners presented before. N.B. There is no gtk_widget_show() function for the toolbar here, as it is designed to be kept hidden when application starts.

/* Create the find toolbar */
void create_find_toolbar(AppData * view)
{
    HildonFindToolbar *find_toolbar;
    find_toolbar = HILDON_FIND_TOOLBAR
        (hildon_find_toolbar_new("Find String: "));
    /* Add signal listers to "Search" and "Close" buttons */
    g_signal_connect(G_OBJECT(find_toolbar), "search",
                     G_CALLBACK(find_tb_search), view);
    g_signal_connect(G_OBJECT(find_toolbar), "close",
                     G_CALLBACK(find_tb_close), view);
    hildon_window_add_toolbar(view->window, GTK_TOOLBAR(find_toolbar));
    /* Set variables to AppData */
    view->find_toolbar = find_toolbar;
    view->find_visible = FALSE;
}

The function create_toolbar() is very similar to the earlier example, only a callback is added for the Find toolbar button. Also, instead of finding, the find button in the main toolbar just shows or hides the find_toolbar

/* Create the toolbar for the main view */
static void create_toolbar(AppData * appdata)
{
    /* Create needed variables */
    GtkWidget *main_toolbar;
    GtkToolItem *tb_new;
    GtkToolItem *tb_open;
    GtkToolItem *tb_save;
    GtkToolItem *tb_find;
    GtkToolItem *tb_close;
    GtkToolItem *tb_separator;
    GtkToolItem *tb_comboitem;
    GtkComboBox *tb_combo;
    /* Create toolbar */
    main_toolbar = gtk_toolbar_new();
    /* Create toolbar button items */
    tb_new = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
    tb_open = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN);
    tb_save = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE);
    tb_find = gtk_tool_button_new_from_stock(GTK_STOCK_FIND);
    tb_close = gtk_tool_button_new_from_stock(GTK_STOCK_CLOSE);
    /* Create toolbar combobox item */
    tb_comboitem = gtk_tool_item_new();
    tb_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
    gtk_combo_box_append_text(tb_combo, "Entry 1");
    gtk_combo_box_append_text(tb_combo, "Entry 2");
    gtk_combo_box_append_text(tb_combo, "Entry 3");
    /* Select second item as default */
    gtk_combo_box_set_active(GTK_COMBO_BOX(tb_combo), 1);
    /* Make combobox to use all available toolbar space */
    gtk_tool_item_set_expand(tb_comboitem, TRUE);
    /* Add combobox inside toolitem */
    gtk_container_add(GTK_CONTAINER(tb_comboitem), GTK_WIDGET(tb_combo));
    /* Create separator */
    tb_separator = gtk_separator_tool_item_new();
    /* Add all items to toolbar */
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_new, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_separator, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_open, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_save, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_comboitem, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_find, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_close, -1);
    /* Add signal lister to "Close" button */
    g_signal_connect(G_OBJECT(tb_close), "clicked",
                     G_CALLBACK(tb_close_cb), NULL);
    /* Add signal lister to "Find" button */
    g_signal_connect(G_OBJECT(tb_find), "clicked",
                     G_CALLBACK(tb_find_cb), appdata);
    /* Add toolbar HildonWindow */
    hildon_window_add_toolbar(appdata->window, GTK_TOOLBAR(main_toolbar));
    gtk_widget_show_all(main_toolbar);
    appdata->main_toolbar = main_toolbar;
}

Then the main application loop creates a new AppData structure and stores the newly-created HildonProgram and HildonWindow into it. Both toolbars are created, but only the main toolbar is set visible.

/* Main application */
int main(int argc, char *argv[])
{
    /* Create needed variables */
    HildonProgram *program;
    HildonWindow *window;
    AppData *appdata;
    GtkWidget *label;
    /* Initialize the GTK. */
    gtk_init(&argc, &argv);
    /* Create the Hildon program and setup the title */
    program = HILDON_PROGRAM(hildon_program_get_instance());
    g_set_application_name("HildonFindToolbar Example");
    /* Create HildonWindow and set it to HildonProgram */
    window = HILDON_WINDOW(hildon_window_new());
    hildon_program_add_window(program, window);
    /* Add example label to window */
    label = gtk_label_new("HildonFindToolbar Example");
    gtk_container_add(GTK_CONTAINER(window), label);
    /* Create AppData */
    appdata = g_new0(AppData, 1);
    appdata->program = program;
    appdata->window = window;
    appdata->label = label;
    /* Create toolbar for view */
    create_toolbar(appdata);
    /* Create find toolbar, but keep it hidden */
    create_find_toolbar(appdata);
    /* Connect signal to X in the upper corner */
    g_signal_connect(G_OBJECT(appdata->window), "delete_event",
      G_CALLBACK(item_close_cb), NULL);
    /* Show all other widgets except the find toolbar */
    gtk_widget_show_all(GTK_WIDGET(appdata->window));
    gtk_widget_hide_all(GTK_WIDGET(appdata->find_toolbar));
    /* Begin the main application */
    gtk_main();
    /* Exit */
    return 0;
}

To compile and run, start Xephyr server and


$ gcc -o example_findtoolbar example_findtoolbar.c `pkg-config gtk+-2.0 hildon-1 --cflags 
--libs` -Wall
$ run-standalone.sh ./example_findtoolbar


Now the application is ready; the screenshot below presents the result. The Find toolbar can be opened and closed by pressing the 'Find' toolbar button (second from right). As no real functionality was created for the search, pressing "Find" only prints the search string into the command line.

Image example_findtoolbar

6.5 Other Hildon Widgets

This section introduces general Hildon widgets, which can and should be used in different maemo applications. Only the most common ones are introduced; see Hildon API documentation [57] for the full list of available widgets. Remember that all normal GTK+ widgets can also be used; see GTK+ Reference Manual at [41].

6.5.1 HildonFileChooserDialog

#include <hildon/hildon-program.h>
#include <hildon/hildon-file-chooser-dialog.h>
#include <gtk/gtk.h>
/* Application UI data struct */
typedef struct _AppData AppData;
struct _AppData {
    HildonProgram *program;
    HildonWindow *window;
    GtkWidget * main_vbox;
    GtkWidget * label;
    /* Menu stuff */
    GtkMenu * main_menu;
    GtkWidget * menu_item_file_open;
    GtkWidget * menu_item_file_save;
    GtkWidget * menu_item_quit;
};

HildonFileChooserDialog is a dialog used to save and open the user files. It is based on the GtkFileChooser, so the API is similar to the one used in GTK+.

HildonFileChooserDialog is usually opened by event callback of "Open" or "Save" menu entries, or toolbar buttons. To use the file dialog, these callbacks should include similar code as below (example_file_chooser.c).

void cb_example_file_open (GtkWidget * w, AppData * data)
{
    gchar *filename = NULL;
    filename = interface_file_chooser (data, GTK_FILE_CHOOSER_ACTION_OPEN);
    if (filename == NULL) {
        filename = "NULL";
    }
    example_show_test_result (data, "Open File", filename);
}
void cb_example_file_save (GtkWidget * w, AppData * data)
{
    gchar *filename = NULL;
    filename = interface_file_chooser (data, GTK_FILE_CHOOSER_ACTION_SAVE);
    if (filename == NULL) {
        filename = "NULL";
    }
    else {
        FILE * f = fopen (filename, "w");
        fprintf (f, "This file was generated by Hildon File Chooser example.");
        fclose (f);
    }
    example_show_test_result (data, "File saved as", filename);
}

The interface_file_chooser() function creates a dialog, using the given GtkFileChooserAction:

gchar * interface_file_chooser (AppData * appdata, GtkFileChooserAction action)
{
    GtkWidget *dialog;
    gchar *filename = NULL;
    dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (appdata->window), action);
    gtk_widget_show_all (GTK_WIDGET (dialog));
    if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
        filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
    }
    gtk_widget_destroy(dialog);
    return filename;
}

To compile and run this program, include additional hildon-fm-2 to the pkg-config path, as follows:


$ gcc -o example_file_chooser example_file_chooser.c  `pkg-config gtk+-2.0 hildon-1 
hildon-fm-2 --cflags --libs`  -Wall 
$ run-standalone.sh ./example_file_chooser


Below are screenshots of both Save and Open dialogs.

Image example_savefile_dialog

Image example_openfile_dialog

For more information about HildonFileChooserDialog, see Hildon FM Reference Manual [57] or in section 6.11.

6.5.2 HildonColorChooser

N.B. In Bora, it was named HildonColorSelector, and from Chinook onwards, the name is changed to HildonColorChooser

#include <hildon/hildon-program.h>
#include <hildon/hildon-color-chooser-dialog.h>
#include <hildon/hildon-color-button.h>
#include <gtk/gtk.h>

HildonColorChooser is a dialog for selecting colors. It is quite similar to GtkColorSelectionDialog, but more suitable for embedded devices.

There are two different ways to use HildonColorChooser: either by using HildonColorButton, showing the selected color in the button area and launching HildonColorChooser dialog automatically when clicked, or by creating the dialog manually.

First, the HildonColorButton should be created (example_color_chooser.c). It can be created by hildon_color_button_new(), and attached to wherever wanted in the UI.

color_button = HILDON_COLOR_BUTTON(hildon_color_button_new());
g_signal_connect(G_OBJECT(color_button), "clicked",
    G_CALLBACK(color_button_clicked), NULL);

In the color_button_clicked callback function, the selected color is retrieved from the HildonColorButton using the "color" property of the widget.

void color_button_clicked(GtkWidget * widget, gpointer data)
{
    GdkColor *new_color = NULL;
    g_object_get(widget, "color", &new_color, NULL);
}

If HildonColorButton is not needed, HildonColorChooser can be created manually:

void ui_show_color_chooser(GtkWidget * widget, AppData * appdata)
{
    GdkColor color = {0, 0, 0, 0};
    GtkWidget *selector;
    gint result;
    selector = hildon_color_chooser_dialog_new();
    /* Set the current selected color to selector */
    hildon_color_chooser_dialog_set_color(
		    HILDON_COLOR_CHOOSER_DIALOG(selector),
                    &color);
    /* Show dialog */
    result = gtk_dialog_run(GTK_DIALOG(selector));
    /* Wait for user to select OK or Cancel */
    switch (result) {
    case GTK_RESPONSE_OK:
        /* Get the current selected color from selector */
        hildon_color_chooser_dialog_get_color
            (HILDON_COLOR_CHOOSER_DIALOG(selector), &color);
        /* Now the new color is in 'color' variable */
        /* Use it however suitable for the application */
        gtk_widget_destroy(selector);
        break;
    default:
        /* If dialog didn't return OK then it was canceled */
        gtk_widget_destroy(selector);
        break;
    }
}

In HildonColorChooser, a pre-defined color can be selected, or a new color can be generated. Both are presented in the screenshots below.

Image example_colorselector_dialog1

Image example_colorselector_dialog2

To compile and run this program:


$ gcc -o example_color_chooser example_color_chooser.c  `pkg-config gtk+-2.0 hildon-1 
--cflags --libs`  -Wall 
$ run-standalone.sh ./example_color_chooser


6.5.3 HildonFontSelectionDialog

#include <hildon/hildon-program.h>
#include <hildon/hildon-font-selection-dialog.h>
#include <gtk/gtk.h>

Where HildonColorChooser provides methods to query color from the user, HildonFontSelectionDialog is used to define font. It contains three tabs with the different font formatting information, and is ideally used in text editors and other applications, where the font style can be selected (example_font_selector.c). To be able to use HildonFontSelectionDialog, some knowledge of Pango text formatting is required, as the returned variable is of type PangoAttrList. For more information about Pango, see Pango Reference Manual[83].

void callback_font_selector(GtkWidget * widget, gpointer data)
{
    HildonFontSelectionDialog *dialog;
    gint result;
    AppData *appdata = (AppData *) data;
    /* Create dialog */
    dialog = HILDON_FONT_SELECTION_DIALOG
        (hildon_font_selection_dialog_new(GTK_WINDOW(appdata->window), "Font selector"));
    /* Show the dialog */
    gtk_widget_show_all(GTK_WIDGET(dialog));
    /* Wait for user to select OK or Cancel */
    result = gtk_dialog_run(GTK_DIALOG(dialog));
    if (result == GTK_RESPONSE_OK) {
        /* Get selected font from dialog */
        /* Use it however suitable for the application */
    }
    /* Close the dialog */
    gtk_widget_destroy(GTK_WIDGET(dialog));
}

To compile and run this program:


$ gcc -o example_font_selector example_font_selector.c `pkg-config gtk+-2.0 hildon-1 
hildon-fm-2 --cflags --libs` -Wall 
$ run-standalone.sh ./example_font_selector


The screenshot below shows the HildonFontSelectionDialog with the Style tab open.

Image example_fontselector

6.5.4 HildonFileDetailsDialog

#include <hildon/hildon-program.h>
#include <hildon/hildon-file-details-dialog.h>
#include <gtk/gtk.h>
/* Application UI data struct */
typedef struct _AppData AppData;
struct _AppData {
    HildonProgram *program;
    HildonWindow *window;
};

HildonFileDetailsDialog is a dialog to show file information. It has two tabs: the first one containing file name, size, last modification date, and some general information; and the second one being empty for the use of applications. These kinds of dialogs are used not only in a file manager application, but also in all kinds of applications with user files, such as editors and viewers.

The following source code (example_file_details.c) describes how to create HildonFileDetailsDialog. Remember to make sure the file exists first. A HildonFileSystemModel is needed.

/* This variable will get filled with the fullpath of the
 * filename that was called to run this program
 * and it will be inspected by the file details dialog*/
static char *global_running_file;
#define TEXT_FILE "/MyDocs/.example/foo.txt"
/* Create a Hildon File System Model to be used in the File details dialog */
static HildonFileSystemModel* get_file_system_model(GtkWidget *ref_widget)
{
 return HILDON_FILE_SYSTEM_MODEL(g_object_new(HILDON_TYPE_FILE_SYSTEM_MODEL,
                                              "ref_widget", ref_widget, NULL));
}

This is how the file details dialog is created. A specific file TEXT_FILE (whose fullpath is global_running_file) is attached to the HildonFileSystemModel object which is attached to the file details dialog.

void callback_file_details(GtkWidget * widget, AppData * data)
{
    HildonFileDetailsDialog *dialog;
    GtkWidget *label = NULL;
    HildonFileSystemModel *model = NULL;
    gint result;
    GtkTreeIter iter;

    /* Create a hildon file system model */
    if( (model = get_file_system_model(GTK_WIDGET(data->window)) ) == NULL)
    {
       g_print("could not get file system model\n\n");
       return;
    }
    /* Load file uri */
    if( !hildon_file_system_model_load_uri( model,
                                            TEXT_FILE,
                                            &iter ) ) 
    {
        g_print("couldnot load uri %s\n", TEXT_FILE);
        return;
    }
    /* Create dialog */
    dialog = HILDON_FILE_DETAILS_DIALOG
        (hildon_file_details_dialog_new_with_model(GTK_WINDOW(data->window), model));

    hildon_file_details_dialog_set_file_iter
        (HILDON_FILE_DETAILS_DIALOG(dialog), &iter);
    /*.....*/
}

The file details dialog can also have an additional application-specific tab. In this example, the extra tab only reshows the file name; but in a real application, it could contain line count, preview of a document, length of the sound, etc. The type of the object "additional tab" is GTK_LABEL, so all kind of widgets can be inserted inside it.

/* This variable will get filled with the fullpath of the
 * filename that was called to run this program
 * and it will be inspected by the file details dialog*/
static char *global_running_file;
#define TEXT_FILE "/MyDocs/.example/foo.txt"
void callback_file_details(GtkWidget * widget, AppData * data)
{
    /*.....*/
    /* Set the dialog to show tabs */
    g_object_set(dialog, "show-tabs", TRUE, NULL);

    /* Create some content to additional tab */
    label =
        gtk_label_new(g_strdup_printf
                      ("Name of this \nfile really is:\n%s", global_running_file)
        );

    /* Add content to additional tab label */
    g_object_set(dialog, "additional-tab", label, NULL);

    /* Change tab label */
    g_object_set(dialog, "additional-tab-label", "More Info", NULL);

    /*.....*/
}

Then the dialog is run just like any other GTK dialog.

    /* Show the dialog */
    gtk_widget_show_all(GTK_WIDGET(dialog));
    /* Wait for user to select OK or CANCEL */
    result = gtk_dialog_run(GTK_DIALOG(dialog));

Here is an example screenshot of the File Details dialog.

Image example_file_details_dialog

To compile and run, start Xephyr server and:


$ gcc -o example_file_details example_file_details.c  `pkg-config gtk+-2.0 hildon-1 
hildon-fm-2 --cflags --libs`  -Wall
$ run-standalone.sh ./example_file_details


6.5.5 HildonBanner

#include <hildon/hildon-program.h>
#include <hildon/hildon-banner.h>
#include <gtk/gtk.h>

HildonBanners are used in many places in maemo applications. They show a small information banner in the top right-hand corner of the application view for a few seconds, and then disappear automatically. They are used for all kinds of notifications, such as "File Saved", "Unable to make connection" or "Please choose only one option".

There are multiple functions to choose from, depending on the information that is wanted to be shown. The banner can show plain text, custom icon, or progress bar to the user. The next example (example_banner.c) presents all these alternatives. First the function which shows the different HildonBanners one by one.

static gint banner_type = 1;
GtkWidget *banner = NULL;
/* Callback to show information banners */
void show_banner(GtkButton * widget, HildonWindow * window)
{
    switch (banner_type) {
    case 1:
        /* Show normal information banner and this automatically goes away */
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Hi there!");
        break;
    case 2:
        /* Information banner with animation icon. 
         * This banner does not automatically disapear. */
        banner = hildon_banner_show_animation(GTK_WIDGET(window), NULL, 
                                              "This is animation icon");
        break;
    case 3:
        /* Remove current information banner */
        gtk_widget_destroy(GTK_WIDGET(banner));
        break;
    case 4:
        /* Information banner with progressbar */
        banner = hildon_banner_show_progress(GTK_WIDGET(window), NULL, 
                                             "Info with progress bar");
        /* Set bar to be 20% full */
        hildon_banner_set_fraction(HILDON_BANNER(banner), 0.2);
        break;
    case 5:
        /* Set bar to be 80% full */
        hildon_banner_set_fraction(HILDON_BANNER(banner), 0.8);
        break;
    case 6:
        /* With sixth click, end the application */
        gtk_main_quit();
    }
    /* Increase the counter */
    banner_type++;
}

The main function introduces how to pack widgets inside the HildonWindow using GtkVBox, which is a vertical box container in GTK+. Inside the vbox, it places one button, which is connected to the show_banner callback function.

/* Main application */
int main(int argc, char *argv[])
{
    /* Create needed variables */
    HildonProgram *program;
    HildonWindow *window;
    GtkWidget *main_vbox;
    GtkWidget *button1;
    /* Initialize the GTK. */
    gtk_init(&argc, &argv);
    /* Create the hildon program and setup the title */
    program = HILDON_PROGRAM(hildon_program_get_instance());
    g_set_application_name("App Title");
    /* Create HildonWindow and set it to HildonProgram */
    window = HILDON_WINDOW(hildon_window_new());
    hildon_program_add_window(program, window);
    /* Add vbox to appview */
    main_vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(window), main_vbox);
    /* Add button to vbox */
    button1 = gtk_button_new_with_label("Show Info");
    gtk_box_pack_start(GTK_BOX(main_vbox), button1, FALSE, TRUE, 0);
    /* Add signal listener to button */
    g_signal_connect(G_OBJECT(button1), "clicked",
                     G_CALLBACK(show_banner), window);
    /* Connect signal to X in the upper corner */
    g_signal_connect(G_OBJECT(window), "delete_event",
      G_CALLBACK(gtk_main_quit), NULL);
    /* Begin the main application */
    gtk_widget_show_all(GTK_WIDGET(window));
    gtk_main();
    /* Exit */
    return 0;
}

Using HildonBanner widgets is very simple, as the HildonWindow takes care of showing them. The output of the application can be seen in the screenshots below. When clicking the button, a normal banner appears first, then one with the custom icon, and at last the progress bar. The last click closes the application.

Image example_banner_note

Image example_banner_animation

Image example_banner_progressbar

There are a lot more Hildon widgets available; to get more information about them, see the Hildon API documentation. To compile and run, start Xephyr server and:


$ gcc -o example_banner example_banner.c  `pkg-config gtk+-2.0 hildon-1 --cflags --libs`  
-Wall
$ run-standalone.sh ./example_banner


6.6 Using Maemo Input Mechanism

6.6.1 Touchscreen (Mouse)

The maemo platform is designed to be used with a touchscreen, and the SDK with a mouse to emulate the touchscreen functionality. Some aspects to remember:

With this information, it is possible to fully emulate touchscreen behavior with the mouse in the maemo SDK, so that there should not be any problem when using the application in the actual device.

6.6.2 Context Sensitive Menu

Context sensitive menus are implemented in the GtkWidget class. An application may register these menus for its widgets, accessed by tap-and-hold over the widget.

An example on how to add this functionality can be found in example_context.c.

6.6.3 Hardware Keys

#include <hildon/hildon-program.h>
#include <hildon/hildon-banner.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

The maemo device has hardware keys, which are also supported in the SDK using different keyboard events. The actual number and type of keys depends on the device, but the common ones are presented in the table below.

Button Description Keyboard Key Event
Image hkey_up Move up Arrow key up HILDON_HARDKEY_UP
Image hkey_down Move down Arrow key down HILDON_HARDKEY_DOWN
Image hkey_left Move left Arrow key left HILDON_HARDKEY_LEFT
Image hkey_right Move right Arrow key right HILDON_HARDKEY_RIGHT
Image hkey_enter Select, Confirm Return HILDON_HARDKEY_SELECT
Image hkey_cancel Cancel, Close Esc HILDON_HARDKEY_ESC
Image hkey_menu Open menu F4 HILDON_HARDKEY_MENU
Image hkey_home Show Home F5 HILDON_HARDKEY_HOME
Image hkey_fullscreen Full screen F6 HILDON_HARDKEY_FULLSCREEN
Image hkey_zoomIn Increase / Zoom in / Volume up F7 HILDON_HARDKEY_INCREASE
Image hkey_zoomOut Decrease / Zoom out / Volume down F8 HILDON_HARDKEY_DECREASE

Adding support for a wider number of keys is easy, as the actual key pressing signals can be implemented with GDK key events. An example (example_hard_keys.c) of this mapping is presented below, first the callback function that is called whenever keys are pressed.

/* Callback for hardware keys */
gboolean key_press_cb(GtkWidget * widget, GdkEventKey * event,
                      HildonWindow * window)
{
    switch (event->keyval) {
    case HILDON_HARDKEY_UP:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Navigation Key Up");
        return TRUE;
    case HILDON_HARDKEY_DOWN:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Navigation Key Down");
        return TRUE;
    case HILDON_HARDKEY_LEFT:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Navigation Key Left");
        return TRUE;
    case HILDON_HARDKEY_RIGHT:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Navigation Key Right");
        return TRUE;
    case HILDON_HARDKEY_SELECT:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Navigation Key select");
        return TRUE;
    case HILDON_HARDKEY_FULLSCREEN:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Full screen");
        return TRUE;
    case HILDON_HARDKEY_INCREASE:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Increase (zoom in)");
        return TRUE;
    case HILDON_HARDKEY_DECREASE:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Decrease (zoom out)");
        return TRUE;
    case HILDON_HARDKEY_ESC:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Cancel/Close");
        return TRUE;
    }
    return FALSE;
}

In the main function, the callback function is connected with g_signal_connect to listen to "key_press_event" signal. Whenever a hardware key is pressed, key_press_cb function is called to process the event.

int main( int argc, char* argv[] )
{
    ...
    /* Add hardware button listener to application */
    g_signal_connect(G_OBJECT(window), 
        "key_press_event", G_CALLBACK(key_press_cb), window);
    ...
}

The output of the hardware key example application is shown below in the case zoom in (F7) is pressed.

Image example_hkey_zoomIn

6.6.4 Maemo's Gtk Accelerator and Mnemonics

6.6.4.1 GtkAccelerator

Just like the standard GTK+ lib, Hildon can also recognize accelerator keys. For example, to recognize and handle when Ctrl+g or Ctrl+G is pressed.

   guint modifiers;
   modifiers = gtk_accelerator_get_default_mod_mask ();
   if ((event->state & modifiers) == GDK_CONTROL_MASK)
      && (event->keyval == GDK_g || event->keyval == GDK_G))
   {
      hildon_banner_show_information(GTK_WIDGET(window), NULL, 
                                     "Ctrl+g or Ctrl+G key combination");
   }

More information on the GTK+ key events can be found at GDK Key Values[25].

Maemo 4.x also supports GtkAcceleratorGroup, which is a group of keyboard accelerators, typically attached to a toplevel Gtk Window. To learn more about GtkAccelerator, look at GTK+ Accelerator Groups[39]. As an example, usually Gtk Applications have "Ctrl + Q" as a shortcut for menu item "Quit" to quit the application. Following is how it is implemented in maemo 4.x.

  1. First of all, create a GtkAccelGroup and add it to the main window
        GtkAccelGroup* accel_group;
        accel_group = gtk_accel_group_new ();
        gtk_window_add_accel_group(GTK_WINDOW(main_window),
                                       accel_group);
  2. Create a sample menu item "Quit" window
    #define ACCEL_PATH_ROOT "/menu"
    #define ACCEL_PATH_QUIT ACCEL_PATH_ROOT"/item_quit"
        /* Create new main menu */
        main_menu = gtk_menu_new();
        item_quit = gtk_menu_item_new_with_label("Quit");
        /* Attach the menu item to the Gtk Accelerator defined above */
        gtk_menu_set_accel_group( GTK_MENU(main_menu),
                                     accel_group);
        gtk_menu_item_set_accel_path( GTK_MENU_ITEM(item_quit),
                                          ACCEL_PATH_QUIT);
        gtk_menu_append(main_menu, item_quit);

    The accel_group is attached to the main menu, which contains the "Quit" menu item. Also, the item_quit is attached with an accelerator path, which is a constant string "<example>/menu/item_quit". Basically, this string is a unique string that identifies the menu item "Quit" of the current application. For example, you can also have "<example>/menu/item_new" as a valid accelerator path for "Open new file" menu item. Please read on for more information on accelerator path.

    More information on how to attach an Accelerator with a Menu item can be found in GTK's documentation.

  3. Define the accelerator path
        gtk_accel_map_add_entry(ACCEL_PATH_QUIT,GDK_q,GDK_CONTROL_MASK);
    

    The above function defines, on the application's level, that a "Ctrl+q" key accelerator is attached to the accelerator path SPMquot;<example>/menu/item_quit". How to define a proper accelerator path and get it mapped is further explained in GTK's documentation.

Now "Ctrl+q" will activate the menu item "Quit".

Additionally, Gtk-Accel must be enabled by a gconf value:


$ gconftool-2 -s /apps/osso/gtk/enable-accels -t bool true


6.6.4.2 Gtk Mnemonics

GTK Mnemonics can be enabled by:


$ gconftool-2 -s /apps/osso/gtk/enable-mnemonics -t bool true


6.6.5 Maemo Text Input Methods

The maemo platform includes text input methods used with touchscreen. There are two different input types: virtual keyboard and handwriting recognition. Only the virtual keyboard is provided in maemo SDK, but as the input is transparent for the application, all applications will automatically support also handwriting recognition. When using the device, it is possible to select between two keyboard layouts. When touching the screen using a finger, a thumb keyboard is provided.

The input methods work automatically, so that whenever text widgets (entry, text view) are pressed, or text inside them selected, the keyboard pops up. In the same way, the keyboard is hidden automatically, when the user selects another widget.

For the development use, it is also possible to use the PC keyboard to input text. It should work without any changes, but if any problems are experienced, it can also be enabled in SDK by clicking the right mouse button on top of a text widget, and then selecting "Input Methods -> X Input Method" from the menu.

6.6.6 Application Layout Considerations

The layout of maemo applications can be described as wide and not very tall, because of the screen dimensions (800x480 pixels). This wideness is emphasized when a text input method is popped up. Therefore, the design of the application UI should follow some rules.

6.7 Writing Control Panel Applets

The control panel is designed to be extendable, and items can be added to it with dynamic libraries with certain functions and desktop files describing the library.

6.7.1 Functions

There are two functions needed for a control panel applet (libapplet.c). These functions are defined in

#include <hildon-cp-plugin/hildon-cp-plugin-interface.h>

The first and more important one, execute, is called when the applet is activated from Control Panel. Usually this function creates a dialog and waits until it is done. Any dialog created should be modal to the window in parameter. N.B. The library might be unloaded when execute returns, so no g_timeouts, gconf_notifys or such should be left when done. Gtk or osso initialization is not needed, as they are already done at this point.

#include <hildon-cp-plugin/hildon-cp-plugin-interface.h>
#include <gtk/gtk.h>
osso_return_t execute(osso_context_t *osso, gpointer data, gboolean user_activated)
{
	GtkWidget *dialog;
	gint response;
	/* Create dialog with OK and Cancel buttons. Leave the separator out,
	 * as we do not have any content. */
	dialog = gtk_dialog_new_with_buttons(
		"Hello control panel",
		GTK_WINDOW(data),
		GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR,
		GTK_STOCK_OK,
		GTK_RESPONSE_OK,
		GTK_STOCK_CANCEL,
		GTK_RESPONSE_CANCEL,
		NULL);
	/* ... add something to the dialog ... */
	if (!user_activated)
	{
		/* ... load state ... */
	}
	/* Wait until user finishes the dialog. */
	response = gtk_dialog_run(GTK_DIALOG(dialog));
	if (response == GTK_RESPONSE_OK)
	{
		/* ... do something with the dialog stuff ... */
	}
	/* Free the dialog (and it's children) */
	gtk_widget_destroy(GTK_WIDGET(dialog));
	return OSSO_OK;
}

The other function is called save_state. It is called when application using the applet is saving state. Usually this does nothing, but if support for state saving is needed, this should be used.

osso_return_t save_state(osso_context_t *osso, gpointer data)
{
	/* ... save state ... */
	return OSSO_OK;
}

6.7.2 Building Applet

To use the applet, it needs to be built into a dynamic library aka shared object. This can be accomplished by giving flag -shared to gcc.


[sbox-DIABLO_X86: ~] > gcc -shared `pkg-config gtk+-2.0 libosso --libs --cflags` libapplet.c 
-o libapplet.so


The binary produced by gcc should be installed to path specified in hildon-control-panel pkg-config entry. This path can be obtained with pkg-config hildon-control-panel -variable=pluginlibdir. By default this is /usr/lib/hildon-control-panel.

6.7.3 The .desktop File

Any control panel applet needs a desktop file describing it. The file contains metadata like name, icon name and library of the applet. The applet desktop file is much like the desktop file for any other application.

Here is an example desktop file (applet.desktop) for the applet created above. Maemo 4.x brings new "Categories" field.

[Desktop Entry] 
Encoding=UTF-8
Version=1.0
Name=Control Panel Hello World
Comment=A control panel example applet
Type=HildonControlPanelPlugin
Icon=qgn_list_cp_isetup
X-control-panel-plugin=libapplet.so
Categories=general
    

The desktop file must be installed to directory specified in pkg-config file for hildon-control-panel. This directory can be obtained by pkg-config hildon-control-panel --variable=plugindesktopentrydir. By default, this is /usr/share/applications/hildon-control-panel.

6.8 Writing Hildon Desktop Plug-ins

6.8.1 Introduction

Image writing_desktop_plugins_home

Hildon Desktop consists of several subcomponents: Task Navigator, Home Area and Status Bar. They support additional plug-ins that are displayed on each these components, extending the functionality of the host application. This section will explain how to write plug-ins for the desktop components using the new GTypeModule-based API. The section also explains the API for writing applets for Control Panel.

6.8.2 Task Navigator Plug-ins

The Task Navigator implements a modular plug-in architecture, allowing binary plug-ins to be loaded. This extends the functionality of the Task Navigator, and is achieved with task buttons. The license for the Task Navigator plug-in can be open source or closed source; in this way, the Task Navigator is versatile and ideal for use in many kinds of devices and applications.

6.8.2.1 Task Navigator Plug-in API

Task Navigator plug-ins can have any widget as their representation on the Task Navigator plug-in area. The plug-in representation, statically visible in the task navigator, can be e.g. GtkButton. Task navigator loads the library and displays the main widget of the plug-in in the plug-in area of the Task Navigator.

The plug-ins are responsible for their own signals, e.g. clicked, toggle and related events, and implementing the callback functions for them. Task navigator only provides a container for the plug-ins; the plug-ins themselves are responsible for implementing their own UI.

6.8.2.2 Desktop File Location

Task Navigator plug-ins must provide a desktop file. It is placed in the directory given by:


 pkg-config osso-af-settings  --variable=tasknavigatordesktopentrydir


and is by default: /usr/share/applications/hildon-navigator/

6.8.2.3 Desktop File Contents

The following describes the required contents of the .desktop file.

The Task Navigator Configuration Control Panel applet is the only one involved with the contents of these .desktop files. Thus, the Task Navigator itself does not read them, and the absence of the file does not prevent the plug-in from working.

The following is an example .desktop file for the Task Navigator applet:

[Desktop Entry]
Name=Hello World
Type=default
X-Path=/usr/lib/hildon-navigator/libhelloworld-tn.so

6.8.2.4 Public Interface

Should look like a usual GObject-based class/object declation. The following is an example implementation:

#ifndef HELLO_NAVIGATOR_PLUGIN_H
#define HELLO_NAVIGATOR_PLUGIN_H

#include <glib-object.h>

/* For Task Navigator plugins */
#include <libhildondesktop/tasknavigator-item.h>

G_BEGIN_DECLS

/* Common struct types declarations */
typedef struct _HelloNavigatorPlugin HelloNavigatorPlugin;
typedef struct _HelloNavigatorPluginClass HelloNavigatorPluginClass;
typedef struct _HelloNavigatorPluginPrivate HelloNavigatorPluginPrivate;

/* Common macros */
#define HELLO_TYPE_NAVIGATOR_PLUGIN            (hello_navigator_plugin_get_type ())
#define HELLO_NAVIGATOR_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
HELLO_TYPE_NAVIGATOR_PLUGIN, HelloNavigatorPlugin))
#define HELLO_NAVIGATOR_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  \
HELLO_TYPE_NAVIGATOR_PLUGIN, HelloNavigatorPluginClass))
#define HELLO_IS_NAVIGATOR_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
HELLO_TYPE_NAVIGATOR_PLUGIN))
#define HELLO_IS_NAVIGATOR_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  \
HELLO_TYPE_NAVIGATOR_PLUGIN))
#define HELLO_NAVIGATOR_PLUGIN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  \
HELLO_TYPE_NAVIGATOR_PLUGIN, HelloNavigatorPluginClass))

/* Instance struct */
struct _HelloNavigatorPlugin
{
    TaskNavigatorItem tnitem;

    HelloNavigatorPluginPrivate *priv;
    GtkWidget              *button;
    GtkWidget              *menu;
};

/* Class struct */
struct _HelloNavigatorPluginClass
{
    TaskNavigatorItemClass parent_class;
};

GType  hello_navigator_plugin_get_type  (void);
GtkWidget *hello_world_button_new (int padding);

G_END_DECLS

#endif /* HELLO_NAVIGATOR_PLUGIN_H */

6.8.2.5 hello_navigator_plugin_init

The function is called upon initialization. It is declared with HD_DEFINE_PLUGIN. It creates the widget that is displayed on the Task Navigator. As mentioned earlier, this is typically a GtkButton. The Task Navigator automatically sizes the button correctly. It also initializes the pop-up menu.

If the button needs to have proper skin, its name should be set as "hildon-navigator-button-one" with gtk_widget_set_name function.

The following is an example implementation:

HD_DEFINE_PLUGIN (HelloNavigatorPlugin, hello_navigator_plugin, TASKNAVIGATOR_TYPE_ITEM);
static void
hello_navigator_plugin_init (HelloNavigatorPlugin *navigator_plugin)
{
  GtkWidget *button;
  button = hello_world_button_new (10);
  navigator_plugin->button=button;
  navigator_plugin->menu=create_menu();
  g_signal_connect (G_OBJECT (button), "clicked",
                      G_CALLBACK (popup_menu), navigator_plugin);
  gtk_widget_set_size_request (button, 80, 80);
  gtk_widget_set_name (button, "hildon-navigator-button-one");
  gtk_widget_show_all (button);
  gtk_container_add (GTK_CONTAINER (navigator_plugin), button);
  gtk_widget_show_all (navigator_plugin);
}
static void
hello_navigator_plugin_class_init (HelloNavigatorPluginClass *class)
{
}

It is advisable to set a pointer to the button in the data that is returned, since it is used later on (navigator_plugin->button).

6.8.2.6 Initializing Menu

The menu is created, and proper pop-up, position and action (show_dialog) functions are defined.

The following is an example implementation:

static void
show_dialog (GtkWidget *item, HelloNavigatorPlugin *thw)
{
    GtkWidget *dialog;
    dialog = gtk_message_dialog_new (NULL,
                                     GTK_DIALOG_MODAL |
                                        GTK_DIALOG_DESTROY_WITH_PARENT,
                                     GTK_MESSAGE_INFO,
                                     GTK_BUTTONS_CLOSE,
                                     "Hello world!");
    gtk_window_set_title (GTK_WINDOW(dialog), "TN Plugin Example");
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);
}
static void
menu_position (GtkMenu *menu,
               gint *x,
               gint *y,
               gboolean *push_in,
               HelloNavigatorPlugin *thw)
{
    g_return_if_fail (thw->button);
    *push_in = TRUE;
    *x = thw->button->allocation.x + thw->button->allocation.width;
    *y = thw->button->allocation.y;
}
static void
popup_menu (GtkWidget *button, HelloNavigatorPlugin *plugin)
{
    if (!plugin->menu)
        return;
    gtk_menu_popup (GTK_MENU (plugin->menu),
                    NULL,
                    NULL,
                    (GtkMenuPositionFunc)menu_position,
                    plugin,
                    0,
                    gtk_get_current_event_time());
}
static GtkWidget*
create_menu (void)
{
    GtkWidget  *menu;
    GtkWidget  *menu_item;
    menu = gtk_menu_new ();
    menu_item = gtk_menu_item_new_with_label ("Hello World!");
    g_signal_connect (G_OBJECT (menu_item), "activate",
                      G_CALLBACK (show_dialog), NULL);
    gtk_menu_append (menu, menu_item);
    /* Name the menu to get the appropriate theming */
    gtk_widget_set_name (menu, "menu_from_navigator");
    gtk_widget_show_all (menu);
    return menu;
}

6.8.2.7 Installing Plug-ins

The plug-in install path can be received using command:


 pkg-config osso-af-settings --variable=hildondesktoplibdir

6.8.3 Home Plug-ins

Home plug-ins are located in the Home Area of the desktop. They can be resized, if the resizability flag X-home-applet-resizable is mentioned in the .desktop file of the plug-in.

6.8.3.1 Desktop File Location

Each home applet needs to provide a .desktop file. The .desktop file location is determined with:


 pkg-config osso-af-settings --variable=homedesktopentrydir


and it defaults to: /usr/share/applications/hildon-home

6.8.3.2 Desktop File Contents

The following is an example hello-world-home.desktop:

[Desktop Entry]
Name=Hello, World!
Comment=Example Home plugin
Type=default
X-Path=libhelloworld-home.so

The mandatory fields are the same as for Task Navigator plug-ins.

6.8.3.3 Home Plug-in Implementation

Home plug-ins should inherit from libhildondesktop's HildonDesktopHomeItem. Here is an example header file:

#ifndef HELLO_HOME_PLUGIN_H
#define HELLO_HOME_PLUGIN_H

#include <glib-object.h>

#include <libhildondesktop/hildon-desktop-home-item.h>

G_BEGIN_DECLS

/* Common struct types declarations */
typedef struct _HelloHomePlugin HelloHomePlugin;
typedef struct _HelloHomePluginClass HelloHomePluginClass;
typedef struct _HelloHomePluginPrivate HelloHomePluginPrivate;

/* Common macros */
#define HELLO_TYPE_HOME_PLUGIN            (hello_statusbar_plugin_get_type ())
#define HELLO_HOME_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
HELLO_TYPE_HOME_PLUGIN, HelloHomePlugin))
#define HELLO_HOME_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  \
HELLO_TYPE_HOME_PLUGIN, HelloHomePluginClass))
#define HELLO_IS_HOME_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
HELLO_TYPE_HOME_PLUGIN))
#define HELLO_IS_HOME_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  \
HELLO_TYPE_HOME_PLUGIN))
#define HELLO_HOME_PLUGIN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  \
HELLO_TYPE_HOME_PLUGIN, HelloHomePluginClass))

/* Instance struct */
struct _HelloHomePlugin
{
	HildonDesktopHomeItem hitem;

	HelloHomePluginPrivate *priv;
};

/* Class struct */
struct _HelloHomePluginClass
{
	HildonDesktopHomeItemClass parent_class;
};

GType  hello_home_plugin_get_type  (void);

G_END_DECLS

#endif

The main functionality consists of required headers, HD_DEFINE_PLUGIN and init functions. The following example creates a button which is defined in the libhelloworld.h header (see example files) and assigns a dialog showing function to it. Instead of a button, any other suitable GTK+ widget may be used. The widget will be shown in the Home view.

#include <glib.h>
#include <gtk/gtk.h>
#include <libhildondesktop/libhildondesktop.h>
#include "hello-world-home.h"
#include "libhelloworld.h"
HD_DEFINE_PLUGIN (HelloHomePlugin, hello_home_plugin, HILDON_DESKTOP_TYPE_HOME_ITEM);
static void
hello_home_plugin_init (HelloHomePlugin *home_plugin)
{
  GtkWidget *button;
  button = hello_world_button_new (10);
  g_signal_connect (button, "clicked",
                    G_CALLBACK (hello_world_dialog_show),
                    NULL);
  gtk_widget_show_all (button);
  /* Set the resizing behavior */
  hildon_desktop_home_item_set_resize_type (HILDON_DESKTOP_HOME_ITEM (home_plugin), HILDON_DESKTOP_HOME_ITEM_RESIZE_BOTH);
  gtk_container_add (GTK_CONTAINER (home_plugin), button);
}
static void
hello_home_plugin_class_init (HelloHomePluginClass *class)
{
}

6.8.3.4 Installing Plug-ins

The plug-in install path can be received using command:


pkg-config osso-af-settings --variable=hildondesktoplibdir

6.8.4 Status Bar Plug-ins

The Status Bar contains status-bar plug-ins. Order of plug-ins and other properties can be set with the Task Navigator and Status Bar configuration applet found on the Control Panel ("Navigation applet").

Status Bar plug-ins are divided into three categories; namely, permanent, conditional and temporal. Permanent plug-ins are shown all the time. Conditional and temporal plug-ins are shown only when the condition is fulfilled.

6.8.4.1 Desktop File Location

The .desktop file shall be placed in the directory given by:


pkg-config osso-af-settings --variable=statusbardesktopentrydir


which defaults to: /usr/share/applications/hildon-status-bar/.

6.8.4.2 Desktop File Contents

Each plug-in has to offer a .desktop file:

[Desktop Entry]
Name=<logical name identifier>
Icon=<logical icon identifier>
Category=<permanent/conditional/temporal>, 
          the default value is permanent if the key value read fails.
X-Path=lib<plugin name>.so
Type=default
Mandatory=<true/false>,
          if not set, the default value is false

Here is an example:

[Desktop Entry]
Name=helloworld_sb
Icon=hello-world
Category=temporal
Type=default
X-Path=libhelloworld_sb.so

6.8.4.3 Header File

Example header file:

#ifndef _HILDON_STATUS_BAR_HELLOWORLD_H_
#define _HILDON_STATUS_BAR_HELLOWORLD_H_

/* StatusbarItem */
#include <libhildondesktop/statusbar-item.h>

/* osso_context_t */
#include <libosso.h>

/* gboolean, gint, G_BEGIN_DECLS/G_END_DECLS */
#include <glib.h>

/* GtkWidget */
#include <gtk/gtk.h>

G_BEGIN_DECLS

/* Every plug-in has a constant priority */
#define HILDON_STATUS_BAR_HELLOWORLD_PRIORITY	1

#define HILDON_STATUS_BAR_HELLOWORLD_ICON_SIZE	40

typedef struct _HildonStatusBarHelloWorld HildonStatusBarHelloWorld;
typedef struct _HildonStatusBarHelloWorldClass HildonStatusBarHelloWorldClass;

#define HILDON_TYPE_STATUS_BAR_HELLOWORLD            \
(hildon_status_bar_helloworld_get_type())
#define HILDON_STATUS_BAR_HELLOWORLD(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
DUMMY_TYPE_STATUS_BAR_HELLOWORLD, HildonStatusBarHelloWorld))
#define HILDON_STATUS_BAR_HELLOWORLD_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  \
DUMMY_TYPE_STATUS_BAR_HELLOWORLD, HildonStatusBarHelloWorldClass))
#define HILDON_IS_STATUS_BAR_HELLOWORLD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
DUMMY_TYPE_STATUS_BAR_HELLOWORLD))
#define HILDON_IS_STATUS_BAR_HELLOWORLD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  \
DUMMY_TYPE_STATUS_BAR_HELLOWORLD))
#define HILDON_STATUS_BAR_HELLOWORLD_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  \
DUMMY_TYPE_STATUS_BAR_HELLOWORLD, HildonStatusBarHelloWorldClass))

struct _HildonStatusBarHelloWorld
{
    StatusbarItem parent;
};

struct _HildonStatusBarHelloWorldClass
{
    StatusbarItemClass parent_class;
};

GType hildon_status_bar_helloworld_get_type(void);

typedef struct
{
    osso_context_t       *osso;         /* osso */
    GtkWidget            *icon;         /* icon in button */
    GtkWidget            *button;       /* button in StatusBar */
} HildonStatusBarHelloWorldPrivate;

G_END_DECLS

#endif /* _HILDON_STATUS_BAR_HELLOWORLD_H_ */

6.8.4.4 Status Bar Functions

The most important function is the init function, which is called when the library is opened. If any g_timeouts or such are used, the IDs of these must be stored for removal. The following example illustrates the init function:

static void hildon_status_bar_helloworld_init(HildonStatusBarHelloWorld *helloworld)
{
    HildonStatusBarHelloWorldPrivate *info = 
        HILDON_STATUS_BAR_HELLOWORLD_GET_PRIVATE(helloworld);

    ULOG_OPEN("hildon-sb-helloworld");

    g_return_if_fail(info);

    info->icon = gtk_image_new_from_pixbuf(NULL);
    info->button = gtk_toggle_button_new();

    set_helloworld_icon("hello-world", info);

    gtk_container_add(GTK_CONTAINER(info->button),
                      GTK_WIDGET(info->icon));

    gtk_container_add(GTK_CONTAINER(helloworld), info->button);

    /* Signal for icon (button) */
    g_signal_connect(G_OBJECT(info->button), "button-press-event",
                     G_CALLBACK(hello_world_dialog_show), NULL);

    /* Initialize osso */
    info->osso = osso_initialize("com.nokia.hildon_sb_helloworld", "1.0", FALSE, NULL);
    if (!info->osso)
        ULOG_WARN("%s: error while initializing osso\n", __FUNCTION__);

    gtk_widget_show_all(GTK_WIDGET(helloworld));
}

6.8.4.5 Destroying Plug-ins

When the item is destroyed, the finalize function is called. The function is defined in class initialization. It should free all the memory and release all the notifications that can lead to the plug-in code. The following example illustrates the finalize function:

static void hildon_status_bar_helloworld_class_init(
		HildonStatusBarHelloWorldClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);
    object_class->finalize = hildon_status_bar_helloworld_finalize;
    g_type_class_add_private(klass, sizeof(HildonStatusBarHelloWorldPrivate));
}
...
static void hildon_status_bar_helloworld_finalize(GObject *object)
{
    HildonStatusBarHelloWorldPrivate *info = HILDON_STATUS_BAR_HELLOWORLD_GET_PRIVATE(object);
    osso_deinitialize(info->osso);
    LOG_CLOSE();
    G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(object)))->finalize(object);
}

There is the possibility of avoiding reloading of plug-ins by setting the plug-in as mandatory in the .desktop file, so that it is never unloaded under any circumstances. If so, the plug-in cannot be deselected in control panel navigator applet.

6.8.4.6 Building Shared Object

The shared object can be built like any normal binary, by only giving the "-shared" flag to gcc:


[sbox-DIABLO_X86: ~] > gcc -shared `pkg-config gtk+-2.0 libosso hildon-1 libhildondesktop 
--libs --cflags` hello-world-statusbar.c -o libhelloworld_sb.so


The binary produced by gcc must be installed to the path specified in the "osso-af-settings pkg-config" entry. This path can be obtained with


pkg-config osso-af-settings --variable=statusbardesktopentrydir


By default, it is /usr/share/applications/hildon-status-bar.

6.8.5 Control Panel Plug-ins

Hildon Control Panel follows the same approach with its plug-ins as the other components in the Hildon Desktop environment. Control panel is a standardized place for putting settings that are changeable by the end users, for applications, servers etc. in the system.

The Control Panel is divided into four categories: General, Connectivity, Personalization and Extras.

6.8.5.1 Desktop File Contents

Each Control Panel applet has to provide a .desktop file in the following format:

[Desktop Entry]
Name=<logical applet name identifier>
Comment=Task Navigator Control Panel Applet
Type=default
Icon=<logical icon name identifier>
Categories=<general/connectivity/personalisation/extras>, from which 
            the extras is the default value, in case the key value 
            read fails. 
X-Path=lib<applet name>.so

6.8.5.2 Init Function

Hildon Control Panel calls the init function as defined by the HD_DEFINE_PLUGIN macro when the plug-in is launched.

6.8.5.3 Control Panel Plug-in Example

Section 6.7 contains a control panel example applet.

6.8.6 Making Plug-in Icons Visible

In order to make the plug-ins icons to show up, the following command must be run:

[sbox-DIABLO_X86: ~] >gtk-update-icon-cache -f /usr/share/icons/hicolor


where /usr/share/icons/hicolor is the directory containing the icons.

To see the Statusbar and Home plugins, you might need to restart the Desktop, for example by rebooting.

To see the Control Panel plugin, you need to restart the Control panel.

To see the Task Navigator plugin, you need to remove the .osso directory in your home directory so that the configuration files are re-created.

When making a .deb package (see section 6.8.7), the above command should be included in debian/postinst and debian/postrm files so the icon cache is automatically updated when installing or uninstalling the package.

6.8.7 Creating Makefiles and Package for Applet

Before reading this section, it is advisable to familiarize with Reference Guide chapters GNU build system 4 and Packaging, Deploying and Distributing 13.

Following the instructions in this document, simple Home applets can be made. The applet basically just contains an eventbox whose state can be saved. The building of applets makes use of autotools: autogen.sh, configure.ac and Makefile.am, which are created in addition to the actual applet source code.

The directory structure of the package is:


/hello_world_applet
/hello_world_applet/debian
/hello_world_applet/data
/hello_world_applet/src


First, the autogen.sh script (this script does not contain any applet-specific details):

#!/bin/sh
set -x
libtoolize -automake
aclocal-1.7 || aclocal
autoconf
autoheader
automake-1.7 -add-missing -foreign || automake -add-missing -foreign

The configure.ac file is as follows:

AC_PREREQ(2.59)
% AC_INIT(maemo-hello-world-applet, 0.1, xxxx@maemo.org)
AM_INIT_AUTOMAKE
AM_CONFIG_HEADER(config.h)
dnl ##################
dnl This script generates host names
dnl ##################
AC_CANONICAL_HOST
dnl ##################
dnl Check for installed programs that is needed
dnl ##################
AC_PROG_CC
AM_PROG_CC_STDC
AC_PROG_INSTALL
AC_PROG_RANLIB
AC_PROG_INTLTOOL([0.21])
AC_PROG_LIBTOOL
AM_PATH_GLIB_2_0
AC_CHECK_PROG(HAVE_PKG_CONFIG, pkg-config, yes, no)
if test "x$HAVE_PKG_CONFIG" = "xno"; then
AC_MSG_ERROR([You need to install pkg-config tool])
fi
dnl ##################
dnl Compiler flags, note that the -ansi flag is not used because
dnl in that case the rint function is not available in math.h
dnl ##################
CFLAGS="$CFLAGS -g -Wall -Werror -ansi -Wmissing-prototypes -Wmissing-declarations"
dnl ##################
dnl Check needed headers, like C standard headers, GLib, GStreamer etc.
dnl ##################
AC_HEADER_STDC
GLIB_REQUIRED=2.6.0
GTK_REQUIRED=2.4.0
LIBOSSO_REQUIRED=0.8.3
PKG_CHECK_MODULES(HELLO, [
glib-2.0 >= $GLIB_REQUIRED,
gtk+-2.0 >= $GTK_REQUIRED,
libhildondesktop,
])
AC_SUBST(HELLO_LIBS)
AC_SUBST(HELLO_CFLAGS)
dnl ##################
dnl directories
dnl ##################
localedir=`pkg-config osso-af-settings -variable=localedir`
AC_SUBST(localedir)
pluginlibdir=`pkg-config osso-af-settings -variable=hildondesktoplibdir`
AC_SUBST(pluginlibdir)
AC_CONFIG_FILES([Makefile
src/Makefile
data/Makefile
])
AC_OUTPUT

The package config file is used to provide information about the installation directory of the compiled plug-in by querying the value of pluginlibdir variable. All version numbers for various components above should be interpreted as illustrative only.

The master Makefile.am is:

SUBDIRS = src data

EXTRA_DIST= \
autogen.sh \
intltool-extract.in \
intltool-merge.in \
intltool-update.in \
debian/rules \
debian/control \
debian/copyright \
debian/changelog \
debian/maemo-hello-world-applet.install 

deb_dir = $(top_builddir)/debian-build

INCLUDES = $(DEPS_CFLAGS)

deb:    dist
-mkdir $(deb_dir)
cd $(deb_dir)    &&   tar xzf ../$(top_builddir)/$(PACKAGE)-$(VERSION).tar.gz
cd $(deb_dir)/$(PACKAGE)-$(VERSION)   &&   dpkg-buildpackage -rfakeroot
-rm -rf $(deb_dir)/$(PACKAGE)-$(VERSION)

Makefile.am of the src subdirectory:

pluginlib_LTLIBRARIES = libhello_applet.la

common_CFLAGS = \
$(HELLO_CFLAGS) \
-DPREFIX=\"$(prefix)\" \
-DLOCALEDIR=\"$(localedir)\"

common_LDADD = \
$(HELLO_LIBS) 

libhello_applet_la_LDFLAGS= -module -avoid-version

libhello_applet_la_CFLAGS = $(common_CFLAGS)

libhello_applet_la_LIBADD = $(common_LDADD)

libhello_applet_la_SOURCES = \
hello-world-home.h \
hello-world-home.c \
libhelloworld.h \
libhelloworld.c

Makefile.am of data directory:

homeapplet_desktopdir=`pkg-config osso-af-settings -variable=homedesktopentrydir`
homeapplet_desktop_DATA=hello-world-applet.desktop
EXTRA_DIST=$(homeapplet_desktop_DATA)

The debian/control is as follows:

Source: maemo-hello-world-applet
Section: misc
Priority: optional
Maintainer: Mr Maemo <xxxx@maemo.org>
Build-Depends: libgtk2.0-dev (>=2.4.0-1), pkg-config, libhildondesktop-dev
Standards-Version: 3.6.1
Package: maemo-hello-world-applet
Section: user/internet
Architecture: any
Depends: ${shlibs:Depends},${launcher:Depends}
Description: Maemo Hello World home panel applet 
Home panel applet for showing Hello World.    
The debian/changelog is: 
maemo-hello-world-applet (0.1) experimental; urgency=low
* Created package
-- Mr Maemo <xxxx@maemo.org>  Tue, 30 May 2006 10:04:45 +0200

The debian/rules are:

#!/usr/bin/make -f

# export DH_VERBOSE=1

# These are used for cross-compiling and for saving the configure script
# from having to guess our platform (since we know it already)
DEB_HOST_GNU_TYPE   ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE)
DEB_BUILD_GNU_TYPE  ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE)

CFLAGS = -Wall -g
PACKAGENAME = maemo-hello-world-applet

ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
CFLAGS += -O0
else
CFLAGS += -O2
endif
ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS)))
INSTALL_PROGRAM += -s
endif

config.status: 
dh_testdir
# Add here commands to configure the package.
CFLAGS="$(CFLAGS)" ./configure -host=$(DEB_HOST_GNU_TYPE) -build=$(DEB_BUILD_GNU_TYPE) \
-prefix=/usr -mandir=\$${prefix}/share/man -infodir=\$${prefix}/share/info

build: build-stamp

build-stamp: config.status
dh_testdir

# Add here commands to compile the package.
$(MAKE)

touch build-stamp

clean:
dh_testdir
dh_testroot
rm -f build-stamp configure-stamp

# Add here commands to clean up after the build process.
-$(MAKE) clean

dh_clean 

install: build
dh_testdir
dh_testroot
dh_clean -k 
dh_installdirs

# Add here commands to install the package
$(MAKE) install DESTDIR=$(CURDIR)/debian/tmp

# Build architecture-independent files here.
binary-indep: build install
# We have nothing to do by default.
# Build architecture-dependent files here.
binary-arch: build install
dh_testdir
dh_testroot
#   dh_installchangelogs 
dh_installdocs
#   dh_installexamples
dh_install -v -sourcedir=debian/build
#   dh_installmenu
#   dh_installdebconf   
#   dh_installlogrotate
#   dh_installemacsen
#   dh_installpam
#   dh_installmime
#   dh_installinit
#   dh_installcron
#   dh_installinfo
#   dh_installman
dh_link
dh_strip
dh_compress
dh_fixperms
#   dh_perl
#   dh_python
dh_makeshlibs
dh_installdeb
dh_shlibdeps -V
dh_gencontrol
dh_md5sums
dh_builddeb

binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install configure

The debian/maemo-hello-world-applet.install is:

usr/lib/hildon-desktop/*.so
usr/share/applications/hildon-home/hello-word-applet.desktop

The data/hello-word-applet.desktop is:

[Desktop Entry]
Name=Hello, world
Comment=Example hello
Type=default
X-Path=libhello_applet.so

6.9 Integrating Applications to Maemo Framework

This section explains how to integrate applications to maemo Application Framework, including adding applications to Task Navigator menu and different features provided by the platform.

6.9.1 Getting Application to Task Navigator Menu

6.9.1.1 Maemo .desktop File

To make an application visible in maemo Task Navigator, a Desktop file is needed for the application. This file contains all the essential information needed to show the application entry in the menu, such as name, binary and D-BUS service name. Name of the file should be [application].desktop and location in filesystem "/usr/share/applications/hildon/".

An example_libosso.desktop file for the application looks like this:

[Desktop Entry]
Encoding=UTF-8
Version=1.0
Type=Application
Name=Example libOSSO
Exec=/usr/bin/example_libosso
X-Osso-Service=org.maemo.example_libosso
Icon=qgn_list_gene_default_app
MimeType=application/x-example;

The fields in this file are:

Encoding - The character encoding of the file, should be UTF-8.

Version - Version of the application desktop file.

Type - Type of the entry, should be Application.

Name - The name of the application which will be visible to Task Navigator menu.

Exec - The application binary which will be started from the menu entry. Although D-BUS allows launching of applications by service name, this is useful in order to be able to launch also applications that are not registered as D-BUS services.

X-Osso-Service - D-BUS service name of the application. This will be defined in the [application].service file, see next section.

Icon - Name of icon that represents this application. The icon will be visible in Task Navigator before application name. Application can install its own icon, or use one of the pre-defined icons available in platform. Format of the file should be PNG and the suffix (.png) should not be defined.

MimeType - The MIME type(s) supported by this entry.



Other commonly used fields:

StartupWMClass - Name of the application binary name. This field is optional and only needed when the binary name is different from the D-BUS service name. In this case, if this property is not set, application will not be shown in the Application Switcher of the Task Navigator.

X-Window-Icon - Usually the same as Icon field.

X-Window-Icon-Dimmed - Defines whether the dimmed icon should have a different look than the normal icon.

X-Osso-Type - Type for the exec, usually application/x-executable.

Terminal - Defines whether the program runs in a terminal window.

N.B. These fields are not to be followed by a whitespace.

See the Desktop Entry Specification [14] for more details.

Desktop file itself does not include the location of the application in the Task Navigator menu. So to make an application visible there, also a symbolic link to desktop file is needed in "/etc/others-menu/". This link assigns the order of the entries as well as the possibility that it is not in the root menu, but instead inside some folder. Application should make this link when package is installed.

The link is named by begining with four numbers defining the order of the entry (the smallest comes on top of the menu) and then underscore (_) followed by the application name. To create this link for the test application:


[sbox-DIABLO_X86: /etc/others-menu] > ln -s \
 /usr/share/applications/hildon/example_libosso.desktop \
 /etc/others-menu/0010_example_libosso.desktop


To see the new entry in Task Navigator, restart the GUI with "af-sb-init.sh restart". Example LibOSSO entry should be visible to menu.

Now to launch the application, D-BUS service file is still needed. The next section describes how to create it.

6.9.1.2 D-BUS Service File

D-BUS service file is needed to be able to launch a maemo application and connect it to D-BUS services. One special feature of D-BUS is that if the application is not running already when a D-BUS message is sent to it, D-BUS will start the application automatically. Also only one instance of a D-BUS service can be run at a time, guaranteeing that each application is running only once.

The format of the service file is simpler than desktop file, below is shown what the example_libosso.service looks like.

[D-BUS Service]
Name=org.maemo.example_libosso
Exec=/usr/bin/example_libosso

The fields in this file are:

Name - The D-BUS service name. This needs to be unique, and same as used in application source codes when initializing the application. Usually the name is built with the application provider URL, followed by the application name to minimize the possibility for conflicts with other service files.

Exec - The full path of the application binary to launch when service name gets called.

D-BUS service file is installed in "/usr/share/dbus-1/services/" and maemo GUI needs to be restarted with "af-sb-init.sh restart" before D-BUS daemon recognizes it.

6.9.2 LibOSSO Library

LibOSSO is the basic library, containing required and helpful functions for maemo applications. The full API documentation of LibOSSO is available in Doxygen format.

6.9.2.1 Maemo Initialization

All maemo applications need to be initialized correctly, or they will not work as expected. One symptom of missing initialization is that application starts from Task Navigator, but closes automatically after few seconds.

Initializing application is performed with osso_initialize() function. With this function, the application connects to D-BUS session bus and system bus. osso_initialize() function should only be called once in the application, and the structure of type osso_context_t type that is returned should be stored for later use. First parameter of the function is the application D-BUS name, used also in application D-BUS service file. Second parameter is the application version as a string. Third is the activation type, with TRUE the library assumes that the application binary has been launched by the D-BUS daemon, and thus will connect to the D-BUS activation bus. Fourth parameter is the GLib main-loop context to connect to, NULL should be used for the default context.

osso_context_t * osso_initialize(const gchar *application, const gchar* version, gboolean activation, GMainContext *context)

When application is closing, osso_deinitialize function should be called to close the message bus connection and free all memory allocated by the library.

void osso_deinitialize(osso_context_t *osso)

Below is an example (example_libosso.c) of this; if the initialization does not succeed, the function returns NULL.

#define OSSO_EXAMPLE_NAME    "example_libosso"
#define OSSO_EXAMPLE_SERVICE "org.maemo."OSSO_EXAMPLE_NAME
/* ... */
    osso_context_t *osso_context;
    /* ... */
    /* Initialize maemo application */
    osso_context = osso_initialize(OSSO_EXAMPLE_SERVICE, "0.0.1", TRUE, NULL);
    /* Check that initialization was ok */
    if (osso_context == NULL) {
        return OSSO_ERROR;
    }
    /* ... */
    /* Deinitialize OSSO */
    osso_deinitialize(osso_context);

6.9.2.2 Remote Process Messages

System wide messages in maemo platform are handled with D-BUS system messaging, which is a Remote Process Communication (RPC) method. LibOSSO has own wrappers to normal D-BUS functions to make usage simpler and to maintain backward compatibility. By using D-BUS, applications can send messages from one process to another.

Callback function receiving the messages can be as follows (example_libosso.c), where AppData structure contains the initialized osso_context:

#include <hildon/hildon-program.h>
#include <hildon/hildon-banner.h>
#include <gtk/gtk.h>
#include <libosso.h>
#define OSSO_EXAMPLE_NAME    "example_libosso"
#define OSSO_EXAMPLE_SERVICE "org.maemo."OSSO_EXAMPLE_NAME
#define OSSO_EXAMPLE_OBJECT  "/org/maemo/"OSSO_EXAMPLE_NAME
#define OSSO_EXAMPLE_IFACE   "org.maemo."OSSO_EXAMPLE_NAME
/* ... */
/* Application UI data struct */
typedef struct _AppData AppData;
struct _AppData {
    HildonProgram *program;
    HildonWindow *window;
    osso_context_t *osso_context;
};
/* ... */
/* Callback for normal D-BUS messages */
gint dbus_req_handler(const gchar * interface, const gchar * method,
                      GArray * arguments, gpointer data,
                      osso_rpc_t * retval)
{
    AppData *appdata;
    appdata = (AppData *) data;
    osso_system_note_infoprint(appdata->osso_context, method, retval);
    osso_rpc_free_val(retval);
    return OSSO_OK;
}

To attach this callback function to receive all the normal D-BUS messages which will come to application, osso_rpc_set_default_cb_f function shall be used:

int main(int argc, char *argv[])
{
    osso_return_t result;

    /* ... */
    /* Add handler for session bus D-BUS messages */
    result = osso_rpc_set_cb_f(appdata->osso_context, 
                               OSSO_EXAMPLE_SERVICE, 
                               OSSO_EXAMPLE_OBJECT, 
                               OSSO_EXAMPLE_IFACE,
                               dbus_req_handler, appdata);

    if (result != OSSO_OK) {
        g_print("Error setting D-BUS callback (%d)\n", result);
        return OSSO_ERROR;
    }
    /* ... */

    /* Deinitialize OSSO */
    osso_deinitialize(osso_context);
}

Now the application is ready to receive D-BUS messages, and whenever it receives one, dbus_req_handler function is called to process the message. From another test application (example_message.c), it is possible to send the "HelloWorld" that this application was designed to handle, like this:

/* ... */
#define OSSO_EXAMPLE_NAME    "example_libosso"
#define OSSO_EXAMPLE_SERVICE "org.maemo."OSSO_EXAMPLE_NAME
#define OSSO_EXAMPLE_OBJECT  "/org/maemo/"OSSO_EXAMPLE_NAME
#define OSSO_EXAMPLE_IFACE   "org.maemo."OSSO_EXAMPLE_NAME
#define OSSO_EXAMPLE_MESSAGE "HelloWorld"
/* ... */
ret = osso_rpc_run(osso_context, 
                   OSSO_EXAMPLE_SERVICE, 
                   OSSO_EXAMPLE_OBJECT, 
                   OSSO_EXAMPLE_IFACE, 
                   OSSO_EXAMPLE_MESSAGE, &retval, DBUS_TYPE_INVALID);
/* ... */

When example_libosso_test is started, it sends an "example_message" D-BUS message to org.maemo.example_libosso service, attached to the example_libosso application (See more about D-BUS service files from an earlier section). Now when example_libosso receives the message, it shows a banner.

One nice thing about D-BUS is that the receiving application does not even need to be started: D-BUS can automatically start the application based on its service file, and then pass the message to it!

6.9.2.3 Hardware State Messages

Maemo applications can connect to listen the system D-BUS messages, like "battery low" and "shutdown". When these messages are received, the application may want to ask the user to save files that are open, or react however wanted.

A callback function is defined like below, taking osso_hw_state_t and gpointer as parameters. The changed state can be gotten from state variable (example_libosso.c).

/* Callback for hardware D-BUS events */
void hw_event_handler(osso_hw_state_t *state, gpointer data)
{
    AppData *appdata;
    appdata = (AppData *) data;
    if (state->shutdown_ind)
    {
        hildon_banner_show_information(GTK_WIDGET(appdata->window), NULL, 
                                       "Shutdown event!");
    }
    if (state->memory_low_ind)
    {
        hildon_banner_show_information(GTK_WIDGET(appdata->window), NULL, 
                                       "Memory low event!");
    }
    if (state->save_unsaved_data_ind)
    {
        hildon_banner_show_information(GTK_WIDGET(appdata->window), NULL, 
                                       "Must save unsaved data event!");
    }
    if (state->system_inactivity_ind)
    {
        hildon_banner_show_information(GTK_WIDGET(appdata->window), NULL, 
                                       "Minimize application inactivity event!");
    }
}

To attach this handler, e.g. in application main(), use osso_hw_set_event_cb().

    /* ... */

    /* Add handler for hardware D-BUS messages */
    result = osso_hw_set_event_cb( appdata->osso_context,
        NULL, hw_event_handler, (gpointer) appdata );

    if (result != OSSO_OK)
    {
        g_print("Error setting HW state callback (%d)\n", result);
        return OSSO_ERROR;
    }

    /* ... */

N.B. These hardware events are not sent to the SDK, so testing them is only possible in maemo device.

6.9.2.4 System Exit Message

There is a separate system message apart from the hardware state messages presented earlier, sent when the applications are required to close themselves. Callback for it is like this (example_libosso.c):

/* Callback for exit D-BUS event */
void exit_event_handler(gboolean die_now, gpointer data)
{
    AppData *appdata;
    appdata = (AppData *) data;
    g_print("exit_event_handler called\n");
    /* Do whatever application needs to do before exiting */
    hildon_banner_show_information(GTK_WIDGET(appdata->window), NULL,
                                   "Exiting...");
}

The callback is set (e.g. in application main) as follows.

int main( int argc, char* argv[] )
{
/* ... */
    /* Add handler for Exit D-BUS messages */
    result = osso_application_set_exit_cb(appdata->osso_context,
                                          exit_event_handler,
                                          (gpointer) appdata);
    if (result != OSSO_OK) {
        g_print("Error setting exit callback (%d)\n", result);
        return OSSO_ERROR;
    }
/* ... */
}

Now whenever the system needs to close an application for any reason, the close will be performed gracefully.

6.9.2.5 Application State Saving and Auto Save

State saving is a special feature of the maemo platform. It means that applications save their running state from RAM to permanent memory, such as flash memory, and then end the running application process. This state saving happens when switching out from the application and the device memory is running low. When the user then returns to a backgrounded application, it is started again, and the application state is read from the flash memory, so that the user will not even know that the application was not running all the time.

Auto saving is an addition to state saving for editor-like applications containing user data. This data/document is automatically saved at the same time with state save. The main difference is that the application state is usually just a small amount of variables describing the state where the application was, whereas the auto-saved file can be any file the user was editing. Auto saving guarantees that user files are not lost when e.g. the battery runs out.

Information on how to implement state saving can be found in LibOSSO API document.

6.9.3 Application Settings

#include <gconf/gconf.h>
#include <gconf/gconf-client.h>

Maemo uses GConf for maintaining application settings. GConf is widely used in Gnome desktop environment for this same purpose. Read more about GConf from GConf configuration system[22].

To make compiler and linker find these headers, give additional "pkg-config cflags gconf-2.0" and "pkg-config libs gconf-2.0" parameters for gcc or to Makefile.

GConf needs to be initialized first with gconf_client_get_default() function, returning GconfClient type of variable. After this, settings of different types can be stored with gconf_client_set_ functions and restored with gconf_client_get_ functions.

To keep GConf keys unique in maemo, they should be of type /apps/maemo/[application]/[key], for example: /apps/maemo/hello_maemo/default_font_size.

Example (example_gconf.c) of basic usage for setting and loading GConf values is following:

    GConfClient		*client;
    /* ... */
    /* Initialize GTK. */
    gtk_init(&argc, &argv);
    /* Get the default client */
    client = gconf_client_get_default();
    /*Add GConf node if absent*/
    gconf_client_add_dir (client, "/apps/example_prefs",
                    GCONF_CLIENT_PRELOAD_NONE, NULL);
    /* ... */
    gconf_client_set_string(client, GCONF_KEY, str, NULL );
    /* ... */
    /* Exit */
    g_object_unref (G_OBJECT (client));

The maemo version of GConf is unaltered, so all tips and tricks available from Internet are usable also in maemo.

6.10 MIME Types Mapping

MIME types mapping specifies for the platform which application should handle a given MIME type. A mapping has to be defined in the desktop file of the application by adding to it the MimeType field.

An example_libosso.desktop file for the application looks like the following:

[Desktop Entry]
Encoding=UTF-8
Version=1.0
Type=Application
Name=Example libOSSO
Exec=/usr/bin/example_libosso
X-Osso-Service=org.maemo.example_libosso
Icon=qgn_list_gene_default_app
MimeType=application/x-example;

The last line is the most important one, and specifies that this application can handle the MIME type "application/x-example".

6.10.1 New MIME Type with OSSO Category Extension

If the application is introducing a new MIME type to the system, it is necessary to provide the mime-info XML (see more at http://standards.freedesktop.org/shared-mime-info-spec/) that defines it, in this case an example-mime.xml file for the application looks as follows:

<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"
           xmlns:osso="http://nokia.com/osso/mime-categories">
  <mime-type type="application/x-example">
        <comment>Example application file</comment>
        <osso:category name="images"/>
        <magic priority="50">
               <match type="string" value="FOO" offset="0"/>
        </magic>
        <glob pattern="*.foo"/>
  </mime-type>
</mime-info>

This entry maps one extension and a "magic" string to the ``application/x-example'' MIME type.

N.B. The glob pattern should be given in lowercase.

6.10.2 What is OSSO Category

The platform has a notion of file categories for the user's data files. The available categories are:

A mapping is set up between categories and MIME types, so that the MIME type of a file decides which category it is in. The MIME type setup is handled by the shared-mime-info infrastructure, and the category information is added to that same framework.

Adding a mapping between a category and a number of MIME types is performed much like adding or editing the supported MIME types in the system.

Each application or library that adds a category mapping should add a file in



/usr/share/mime/packages/


The file format is the same XML format used for MIME types, with an added tag "<osso:category>". See the example above, where it sets up a mapping between .foo files and the Images category.

6.10.3 Updating Platform Databases

To introduce the newly defined MIME type(s) to the platform, it is necessary to:

  1. Copy the mime-information XML under /usr/share/mime/packages:

    [sbox-DIABLO_X86: ~] > cp example-mime.xml /usr/share/mime/packages
    
  2. Update the MIME and desktop database:

    [sbox-DIABLO_X86: ~] > update-mime-database /usr/share/mime
    [sbox-DIABLO_X86: ~] > update-desktop-database /usr/share/applications
    
  3. Update the OSSO category database:

    [sbox-DIABLO_X86: ~] > hildon-update-category-database /usr/share/mime
    

If the MIME type is to be removed from the platform, it is done by simply deleting the XML file under /usr/share/mime/packages/, and updating the databases as above.

6.10.4 Registering MIME Type with Package

Since most of the applications are installed on the platform via pre-compiled packages, the MIME type registration has to be performed as well.

The steps are similar to the ones shown above.

To have the MIME information XML installed under /usr/share/mime/packages, the package rules have to be edited and the files installed; in this case it would look as follows:

This way, it can be assured that the mime information XML is being installed under /usr/share/mime/packages.

The following lines have to be added both into the postinst and postrm files of the package:

if [ -x /usr/bin/update-mime-database ]; then
 update-mime-database /usr/share/mime
fi
if [ -x /usr/bin/update-desktop-database ]; then
 update-desktop-database /usr/share/applications
fi
if [ -x /usr/bin/hildon-update-category-database ]; then
 hildon-update-category-database /usr/share/mime
fi

This keeps the platform mime information and OSSO category databases up-to-date.

6.11 Writing Application from Scratch

6.11.1 Introduction

This section is a guide for making new applications for the maemo platform. When starting to write a new application, the first phase is to set up the development environment. The actual coding process is described in this section.

As an example, a simple plain text editor is used, with only a few essential features. Good examples for this kind of plain text editors are gtk2edit and gpe-edit. From now on, the application is called "MaemoPad".

MaemoPad will have some basic features, such as "New", "Open", "Save", "Save As...", "Cut", "Copy", "Paste", "Font", "Full Screen", "Full Screen" hardware key handling, "Send-Via email/bluetooth" and "Close". For simplicity, there will be no features like "Undo", "Redo", different fonts in one document, pictures, tables, etc.

Image Graphic6_shrunk

The figure 6.11.1 is a screenshot from the MaemoPad. As can be seen, MaemoPad application has its own toolbar at the bottom of screen, and a drop-down menu in the upper left-hand corner. The area on the left is used to show the file.

6.11.2 Creating Application File Structure

First, the file structure for MaemoPad has to be created. The structure presented is quite general, and can be used for different kinds of applications on the maemo SDK platform.

The project directory has four subdirectories:

Apart from the four subdirectories, the project main directory includes only three script files, using GNU autoconf and automake tools to configure and compile the project:

The way used to compile a project is usually


 $ ./autogen.sh && ./configure && make;


For more information about GNU autoconf and automake, see chapter GNU build system 4 on Maemo Reference Manual.

The file structure of the MaemoPad application looks like this:


Makefile.am
autogen.sh
configure.ac
src/
        Makefile.am
        appdata.h
        main.c
        ui/
                callbacks.h
                callbacks.c
                interface.h
                interface.c
data/
        Makefile.am
        com.nokia.maemopad.service
        maemopad.desktop
        icons/
                26x26/
                        maemopad.png
                40x40/
                        maemopad.png
                scalable/
                        maemopad.png
        help/
                en_GB/
                        MaemoPad.xml
po/
        Makefile.in.in
        POTFILES.in
        en_GB.po
debian/
        changelog
        control
        copyright
        rules

6.11.3 Coding Application Main

In the src/ directory of MaemoPad, there are main.c and appdata.h.

6.11.3.1 appdata.h

appdata.h defines AppData structure to hold the application data. Even though the data varies depending on the application, a sample AppData struct might look like this:

struct _AppData
{
    AppUIData *ui; /* handle to app's UI */
    HildonProgram *program; /* handle to application */
    osso_context_t *osso; /* handle to osso */
    AppConfData *conf; /*handle to app's Gconf data */
};

Where

Each application will create its own AppData variable, and this variable is usually passed around as a parameter in the functions, particularly the callback functions, so that the applications data can be accessible by the functions.

Many applications declare this AppData variable as global.

MaemoPad's AppData struct is as follows:

struct _AppData
{
    HildonProgram *program; /* handle to application */
    HildonWindow *window; /* handle to app's window */
    osso_context_t *osso; /* handle to osso */
};

N.B. AppUIData in MaemoPad points to AppData, instead of AppData pointing to AppUIData. This is not a significant difference, since the ultimate purpose is to have application data accessible by the functions.

typedef struct _AppUIData AppUIData;
struct _AppUIData
{
    /* Handle to app's data */
    AppData *data;
    /* Fullscreen mode is on (TRUE) or off (FALSE) */
    gboolean fullscreen;
    /* Items for menu */
    GtkWidget *file_item;
    GtkWidget *new_item;
    GtkWidget *open_item;
    GtkWidget *save_item;
    GtkWidget *saveas_item;
    GtkWidget *edit_item;
    GtkWidget *cut_item;
    GtkWidget *copy_item;
    GtkWidget *paste_item;
    /*....more truncated .....*/
}

6.11.3.2 main.c

main.c usually performs, at least, the following functions:

Here is the main function of MaemoPad with comments:

int main( int argc, char* argv[] )
{
    AppData* data; 
    HildonProgram* program; 
    AppUIData* main_view;
    /* Initialize the locale stuff */
    setlocale ( LC_ALL, "" );
    bindtextdomain ( GETTEXT_PACKAGE, LOCALEDIR );
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    textdomain ( GETTEXT_PACKAGE );
    /* Init the gtk - must be called before any hildon stuff */
    gtk_init( &argc, &argv );
    /* Create the hildon application and setup the title */
    program = HILDON_PROGRAM ( hildon_program_get_instance () );
    g_set_application_name ( _("MaemoPad") );
    if(!g_thread_supported()) {
        g_thread_init(NULL);
    }
    /* Create the data and views for our application */
    data = create_data ();
    data->program = program;
    main_view = interface_main_view_new ( data );
    hildon_program_add_window( data->program, data->window );
    g_signal_connect( G_OBJECT(data->window), "delete_event", gtk_main_quit, NULL );    
    /* Connect key presses signals */
    g_signal_connect(G_OBJECT(data->window), "key_press_event",
                       G_CALLBACK(key_press),(gpointer) main_view);
    g_signal_connect(G_OBJECT(data->window), "key_release_event",
                       G_CALLBACK(key_release), (gpointer) main_view);
    /* Begin the main app */
    gtk_widget_show ( GTK_WIDGET ( data->window ) );
    gtk_main();
    /* Clean up */
    interface_main_view_destroy ( main_view );
    destroy_data ( data );
    return 0;
}

6.11.4 User Interface

The graphical user interface code is implemented in directory ./src/ui/. There are two .c files: interface.c and callbacks.c

6.11.4.1 interface.c

This file will create the graphical user interface (GUI) and connect the signals and events to appropriate handlers defined in callbacks.c. See section 6.4 for information on how to create GUI in maemo. If GTK is not familiar, it is advisable to start from the GTK+ Reference Manual [57].

As a general practice, an AppUIData struct variable is created when creating the GUI. And then, a HildonWindow and smaller components are created in different functions, such as create_menu(), create_toolbar(). When creating each component, AppUIData should refer various necessary UI objects created along the way.

The following excerpt shows how AppUIData is created, and how it points to the toolbar and the button "New file" on the toolbar.

/* Creates and initializes a main_view */
AppUIData* interface_main_view_new( AppData *data )
{
    /* Zero memory with g_new0 */
    AppUIData* result = g_new0( AppUIData, 1 );
    /*....*/
    create_toolbar( result ); 
    /*....*/
}
/* Create toolbar to mainview */
static void create_toolbar ( AppUIData *main )
{
    /* Create new GTK toolbar */
    main->toolbar = gtk_toolbar_new ();
    /*....*/
    /* Create the "New file" button in the toolbar */
    main->new_tb = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
    /*....*/
}

6.11.4.2 callbacks.c

callbacks.c defines all the functions that handle the signals or events that might be triggered by the UI. When creating different UI objects in interface.c, the handlers are registered as follows.

/* Create the menu items needed for the drop down menu */
static void create_menu( AppUIData *main )
{
    /* ... */
    main->new_item = gtk_menu_item_new_with_label ( _("New") );
    /* ... */
    /* Attach the callback functions to the activate signal */
    g_signal_connect( G_OBJECT( main->new_item ), "activate", 
                      G_CALLBACK ( callback_file_new), main );
    /* ... */
}

Function callback_file_new is implemented in callbacks.c, saving the current file if needed, and then opening a new file to edit.

void callback_file_new(GtkAction * action, gpointer data)
{
    gint answer;
    AppUIData *mainview = NULL;
    mainview = ( AppUIData * ) data;
    g_assert(mainview != NULL && mainview->data != NULL );
    /* save changes note if file is edited */
    if( mainview->file_edited ) {
        answer = interface_save_changes_note( mainview );
        if( answer == CONFRESP_YES ) {
            if( mainview->file_name == NULL ) {
                mainview->file_name = interface_file_chooser(mainview, GTK_FILE_CHOOSER_ACTION_SAVE);
            }
            write_buffer_to_file ( mainview );
        }
    }
    /* clear buffer, filename and free buffer text */
    gtk_text_buffer_set_text ( GTK_TEXT_BUFFER (mainview->buffer), "", -1 );
    mainview->file_name = NULL;
    mainview->file_edited = FALSE;
}

N.B. The AppUIData struct variable mainview is retrieved in such a way that the handlers can have a direct effect on the UI for the users.

There are many other functions in MaemoPad, explained above:

More information on GTK Widgets can be found on the GTK+ Reference Manual.

6.11.4.3 interface.h

In the interface header file interface.h, public functions are defined for main.c and callbacks.c. In the case of MaemoPad, confirmation responses for the save changes note, MaemopadError enum for the Hildon error note and the AppUIData are also defined here. N.B. AppUIData can also be defined in appdata.h in some other applications. MaemoPad's interface.h looks like this:

#define MAIN_VIEW_NAME "AppUIData"
typedef enum {
    MAEMOPAD_NO_ERROR = 0,
    MAEMOPAD_ERROR_INVALID_URI,
    MAEMOPAD_ERROR_SAVE_FAILED,
    MAEMOPAD_ERROR_OPEN_FAILED
} MaemopadError;
/* Struct to include view's information */
typedef struct _AppUIData AppUIData;
struct _AppUIData
{
    /* Handle to app's data */
    AppData *data;
    /* Fullscreen mode is on (TRUE) or off (FALSE) */
    gboolean fullscreen;
    /* Items for menu */
    GtkWidget *file_item;
    GtkWidget *new_item;
    /* ... */
    GtkWidget *font_item;
    GtkWidget *fullscreen_item;
    /* Toolbar */
    GtkWidget* toolbar;
    GtkWidget* iconw;
    GtkToolItem* new_tb;
    GtkToolItem* open_tb;
    /* ... */
    /* Textview related */
    GtkWidget* scrolledwindow;   /* textview is under this widget */
    GtkWidget* textview;         /* widget that shows the text */
    GtkTextBuffer* buffer;       /* buffer that contains the text */
    GtkClipboard* clipboard;     /* clipboard for copy/paste */
    PangoFontDescription* font_desc;    /* font used in textview */
    gboolean file_edited;     /* tells is our file on view edited */
    gchar* file_name;         /* directory/file under editing */
};
/* Public functions: */
AppUIData* interface_main_view_new( AppData* data );
void interface_main_view_destroy( AppUIData* main );
char* interface_file_chooser( AppUIData* main, GtkFileChooserAction action );
PangoFontDescription* interface_font_chooser( AppUIData * main );
/* ... */

6.11.5 Localization

Localization means translating the application into different languages. In maemo, this is fairly easily performed by grouping all the strings needing translations into a .po file, giving them each an id, and then using the id in the code instead of hard-coded strings. The function used to generate the translated strings from an id in maemo is the standard GNU gettext().

6.11.5.1 Initialization

When the application runs, depending on the locale settings of the system, gettext() will translate the id into the correct language. The application will have to initialize the text domain as follows:

int main( int argc, char* argv[] )
{
    /* ... */
    /* Initialize the locale stuff */
    setlocale ( LC_ALL, "" );
    bindtextdomain ( GETTEXT_PACKAGE, LOCALEDIR );
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    textdomain ( GETTEXT_PACKAGE );
    /* ... */
}

More information on localization can be found in section 12.3.

6.11.5.2 File Structure

Localization files are stored in the po/ directory. The following files will be used for MaemoPad's localization:



Makefile.am
POTFILES.in
en_GB.po


POTFILES.in contains the list of source code files that will be localized. In MaemoPad, only main.c and interface.c contain strings that need to be localized.

# List of MaemoPad source files to be localized
../src/main.c
../src/ui/interface.c

File en_GB.po includes translated text for British English. It contains pairs of id/string as follows:

# ...
msgid "maemopad_yes"
msgstr "Yes"
# ...

N.B. The comments in .po file start with "#".

6.11.5.3 Using en_GB.po

The msgid(s) are passed to the GNU gettext() function as a parameter to generate the translated string. In maemo, the recommended way is

#define _(String) gettext(String)

Therefore, in MaemoPad, the string for Menu->File->Open menu will be created as follows:

    main->open_item = gtk_menu_item_new_with_label ( _("Open") );

6.11.5.4 Creating .po Files from Source

Sometimes it is necessary to localize code for applications, where localization was not considered at the beginning. It is possible to create .po files from source code using GNU xgettext[26], by extracting all the strings from the source files into a template.po file



xgettext -f POTFILES.in -C -a -o template.po


Read the man page for xgettext for more information, in short:

The next step is to copy this template.po into ./po/en_GB.po and add or edit all the strings in British English. Other languages can be handled in the same way.

6.11.6 Adding Application to Menu

See section 6.9.1 for information on how to perform this. In short, the maemopad.desktop and com.nokia.maemopad.service files are stored in the ./data directory, and they look like this, respectively
[Desktop Entry]
Encoding=UTF-8
Version=0.1
Type=Application
Name=MaemoPad
Exec=/usr/bin/maemopad
Icon=maemopad
X-Window-Icon=maemopad
X-Window-Icon-Dimmed=maemopad
X-Osso-Service=com.nokia.maemopad
X-Osso-Type=application/x-executable

(N.B. Whitespace is not allowed after the lines.)

# Service description file
[D-BUS Service]
Name=com.nokia.maemopad
Exec=/usr/bin/maemopad

6.11.7 Link to Maemo Menu

When the Debian package is installed to maemo platform, .desktop and .service files are used to link MaemoPad to the Task Navigator.

6.11.8 Adding Help

Applications can have their own help files. Help files are XML files located under the /usr/share/osso-help directory. Each language is located in its own subdirectory, such as /usr/share/osso-help/en_GB for British English help files. In MaemoPad, there is data/help/en_GB/MaemoPad.xml, having very simple help content. It is mandatory that the middle part of the contextUID is the same as the help file name (without suffix):

<?xml version="1.0" encoding="UTF-8"?>
<hildonhelpsource>
  <folder>
    <title>MaemoPad</title>
    <topic>
      <topictitle>Main Topic</topictitle>
      <context contextUID="Example_MaemoPad_Content" />
      <para>This is a help file with example content.</para>
    </topic>
  </folder>
</hildonhelpsource>

By using hildon_help_show() function (see hildon-help.h), this help content can be shown in the application. After creating a Help menu item, a callback function can be connected to it:

void callback_help( GtkAction * action, gpointer data )
{
    osso_return_t retval;
    /* connect pointer to our AppUIData struct */
    AppUIData *mainview = NULL;
    mainview = ( AppUIData * ) data;
    g_assert(mainview != NULL && mainview->data != NULL );
    retval = hildon_help_show(
      mainview->data->osso, /* osso_context */
      HELP_TOPIC_ID,        /* topic id */
      HILDON_HELP_SHOW_DIALOG);
}

To get more information, see section 6.12.

6.11.9 Packaging Application

A Debian package is an application packed in one file to make installing easy in Debian-based operating systems, like maemo platform. More information about creating a Debian packages can be found in section Creating Debian packages 13.1 of Maemo Reference Manual Chapter Packaging, Deploying and Distributing. Our goal in this section is to create a Debian package of MaemoPad, to be installed in the maemo platform.

If creating a package that can be installed using the Application Manager, see section Making Application Packages 13.2 of the same aforementioned chapter.

6.11.9.1 Creating debian/ Directory

Some files are needed to create the package. They are placed under the debian/ directory. These files will be created:



changelog
control
copyright
rules
... etc ...


The 'rules' file is the file defining how the Debian package is built. The 'rules' file tells where the files should be installed. Also a 'control' file is needed to define what kind of packages (often different language versions) are going to be created. Changelog and copyright files are also needed, or building the package will not work. The 'changelog' file consists of the version number of the package, and a short description about changes compared to older versions. The 'copyright' file includes information in plain text about the package copyrights.

Most important lines in rules file are:

# Add here commands to install the package into debian/<installation directory>
$(MAKE) install DESTDIR=$(CURDIR)/debian/<installation directory>

These lines define, where the package files will be installed. debian/<installation directory> is used as a temporary directory for package construction.

6.11.9.2 Creating and Building Package

The package is made using the following command:



dpkg-buildpackage -rfakeroot -uc -us -sa -D


The result should be these MaemoPad files:

There is now a .deb file. This package can be installed using "fakeroot dpkg -i maemopad_x.x_i386.deb" command. The icon to the application should now be in the maemo Task Navigator menu, and it should be launchable from there. The package can be removed with the command "fakeroot dpkg -r maemopad".

6.12 Help Framework

Various services are available for softwares developed on the maemo platform. One of them is the Help Framework, a context-based help system that can be easily used in any application.

The Help Framework is a centralized way to offer help services to the users of the programs. Maemo platform has an built-n help system that handles all the help documentation for the programs using the Help Framework. Libraries are used to register a program to the Help Framework, and after that, the content of the actual help documentation can be written. An ID tag will be given to the help file, which will be in XML format. This way, it is possible to control which help file will be loaded when the user asks for help: it is only necessary to call the correct help content ID. When using the Help Framework, the help documentation for a program will also be available when using the maemo platform help application.

This section is a short guide for using the Help Framework. It focuses on the explanation of the various steps necessary to enable this feature in an application, by going through a small example of using the framework. The section also explains the XML format used in the actual writing of the help content.

6.12.1 Creating Help File

The help file is an XML document, with various pre-defined tags, used to aid the creation of help topics. The example below shows the format this file must obey:

<?xml version="1.0" encoding="UTF-8"?>
<ossohelpsource>
  <folder>
    <title>Help Framework Example</title>
    <topic>
      <topictitle>Main Topic</topictitle>
      <context contextUID="osso_example_help" />
      <para>This is a help file with example content.</para>
    </topic>
  </folder>
</ossohelpsource>

The <context /> tag is used to define the topic "context", which is needed for referring the document by the help framework. The context string is defined by the attribute contextUID and must follow the format xxx_filename_yyy, where filename must be exactly the name of the XML file (without the extension).

In the example above, contextUID osso_example_help was created, and it will be used in the example application.

In addition to the <context /> tag, there are various other tags. Below is a list of the tags with a short description:

Also common HTML tags can be used, such as <b> for bold text and <i> for italics.

The example XML file can be downloaded from help-framework downloads.

6.12.2 Adding Help Context Support into Application

This section will demonstrate, how to create an application with an online help system. The application will use the XML file described in the previous section.

To use the online help system on the maemo platform, it is necessary to include the following header files into the application:

#include <libosso.h>
#include <hildon/hildon-help.h>

The header file libosso.h provides the environment initialization functions of the maemo platform, which are responsible for registering the context used by the application for the help framework.

The hildon/hildon-help.h file contains prototypes for functions in the library hildon-help. These are used for locating the help topics and displaying these topics to the user.

Next, after including these files into the source code, a couple of pre-compilation macros are defined to help the build in the future. It is also important to define a global structure for the application to save HildonProgram, HildonWindow, and osso_context_t, which are, respectively, application object, main window, and application context object of the program.

#define OSSO_HELP_TOPIC_EXAMPLE "osso_example_help"
/* Application UI data struct */
typedef struct {
    HildonProgram *program;
    HildonWindow *window;
    osso_context_t *osso_context;
} AppData;
AppData appdata;

To start the application, two steps are necessary:

The following example shows in detail, how this initialization must be performed:

#define OSSO_EXAMPLE_NAME    "example_help_framework"
#define OSSO_EXAMPLE_SERVICE "org.maemo."OSSO_EXAMPLE_NAME
/* ... */
    /* Initialize the GTK. */
    gtk_init(&argc, &argv);
    /* Initialize maemo application */
    appdata.osso_context = osso_initialize(OSSO_EXAMPLE_SERVICE, "0.0.1", TRUE, NULL);
    /* Check that initialization was ok */
    if (appdata.osso_context == NULL) {
        return OSSO_ERROR;
    }

Now the hildon help system can be used. To show one of the topics from the help file, the function hildon_help_show() has to be called, as shown in the example.

In case the topic is not available, the function hildon_help_show() will return the error code OSSO_ERROR.

/* handler for Help button */
void help_activated(GtkWidget *win, gchar *help_id)
{
    osso_return_t retval;
    if (!help_id) {
        return;
    }
    retval = hildon_help_show(
        appdata.osso_context, /* global osso_context */
        help_id,              /* topic id */
        HILDON_HELP_SHOW_DIALOG);
}

To create a button to activate help, the following code should be added into the main function:

    /* Add a Help button */
    help_button = gtk_button_new_with_label("Help");
    g_signal_connect(G_OBJECT(help_button), "clicked",
                     G_CALLBACK(help_activated),
                     OSSO_HELP_TOPIC_EXAMPLE);
    g_signal_connect(G_OBJECT(help_button), "clicked",
                     G_CALLBACK(help_activated),
                     OSSO_HELP_TOPIC_EXAMPLE);

The next step is to add a help button into a dialog box (symbol '?' on the upper right-hand corner of the dialog box). The process is quite simple: the button is enabled in the dialog box with the function hildon_help_dialog_help_enable(). The example below shows how this is done.

void button_clicked(GtkWidget *widget, gpointer data) {
    GtkWidget *dialog;
    guint result;
    dialog = hildon_color_chooser_dialog_new();
    /* enable help system on dialog */
    hildon_help_dialog_help_enable(
        GTK_DIALOG(dialog),
        OSSO_HELP_TOPIC_EXAMPLE,
        appdata.osso_context
    );
    result = gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);
    return;
}

Now the only thing remaining is to compile the example:



gcc `pkg-config --cflags --libs hildon-1 libosso hildon-help` \
   -o example_help_framework example_help_framework.c


It is possible to test the help system also in Scratchbox.

6.12.3 Distributing Example Application

To quickly test the help content, the XML file can be manually copied to the correct path (e.g. /usr/share/osso-help/en_GB/), where it can be found by the help library. The directories may have to be created manually, if testing in Scratchbox. If the help file is written for some other language, en_GB in the path should be replaced with the correct language definition. After starting the newly-created application (or Help application), the help content should be available for viewing. The easiest way to distribute the help file and the application is to create an Application Manager package. For more details, please see section Making Application Packages 13.2 in chapter Packaging, Deploying and Distributing of Maemo Reference Manual.



sponsored by Nokia Corporation