How to Write New Applications in maemo 4.0

Table of Contents

Introduction

This document has been reviewed for maemo 4.0.

This document is a guide for making new applications for the maemo platform. When starting to write a new application, the first phase is to setup the development environment, which is explained in the Maemo Tutorial [1]. The actual coding process is described in this document.

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.

Graphic6

Figure 1. MaemoPad application

The Figure 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.

Go to top

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:

  • src/: Contains the source files.
    In the src/ directory, main.c is in the top of the hierarchy in the MaemoPad code. The file appdata.h has the data needed by the application. Writing the code in main.c and appdata.h is explained in the next section. The file interface.c in the src/ui/ directory takes care of creating the graphical user interface. callbacks.c contains the code taking care of the messages between MaemoPad and maemo platform.
  • debian/: Contains the files related to debian packaging [5].
  • data/: Contains data files to be used when running the application; such as icons and help files; and .desktop file to Add MaemoPad to menu, a D-Bus .service file. More details on .service file can be found here
  • po/: Contains the localization files.
    The localization directory po/ has a file POTFILES.in, listing the names of the files to be localized, and a translation file en_GB.po containing British English strings for the application, see localization for more information.

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

  • autogen.sh is a tiny script file to make other script files to build the project.
  • configure.ac includes definitions on how to produce the configure script for the project.
  • Makefile.am includes the files and subdirectories needed to make the application: src/, po/ and data/ and all the Makefiles in src/, po/, and data/ directories.

The way used to compile a project is usually

 $ ./autogen.sh && ./configure && make;
The GNU autoconf and automake are out of the scope of this document, so for more information, references [2] and [3] should be checked.

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
	maemopad.install
	rules
	

Go to top

Coding Application Main

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

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
  • AppUIData is a struct containing pointers to all the UI objects; such as HildonWindow, menu items, toolbar; and UI-related data, for instance a boolean variable indicating whether the application is in fullscreen mode.
  • HildonProgram is explained in the Maemo tutorial. It can, alternatively, be declared inside AppUIData struct.
  • osso_context_t is explained in the Maemo tutorial
  • AppConfData is a struct that contains Gconf-related data of the applications.

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 .....*/
}

main.c

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

  • Initializing GTK
  • Initializing localization
  • Creating an instance of HildonProgram
  • Calling to a function, usually defined in interface.c, to create the main view.
  • Connecting the main view window to the created HildonProgram
  • Running gtk_main()
  • Connecting the "delete_event" of the main view to a callback function to handle a proper application exit, such as destroying the main view window, freeing used memory and saving application states before exit.
  • Call gtk_main_quit()
Here is the main function of MaemoPad with comments:

int main( int argc, char* argv[] )
{
    AppData* data;
    HildonProgram* program;
    MainView* 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") );

    /* 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 );
        
    /* Begin the main app */
    gtk_widget_show ( GTK_WIDGET ( program ) );
    gtk_main();

    /* Clean up */
    interface_main_view_destroy ( main_view );
    destroy_data ( data );

    return 0;
}	  
		

Go to top

User Interface

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

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 MaemoTutorial#gui for information on how to create GUI in maemo. If GTK is not familiar, it is advisable to start from GTK+ Reference Manual [4].

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);
}

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;
	  

Note how the AppUIData struct variable mainview is retrieved, so that the handlers can have effect directly on the UI for the users.

There are many other functions in MaemoPad, explained in Maemo Tutorial:

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

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 );
....

Go to top

Localization

Localization means translating the application into different languages. Simply put: in maemo, this is fairly easy 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().

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 Maemo Localization How-to

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 "#".

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") );

Creating .po Files from Source

Sometimes it is necessary to localize code from applications where localization was not considered at the beginning. It is possible to create .po files from source code using GNU xgettext, 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:

  • "-f POTFILES.in" uses POTFILES.in to get the files to be localized
  • "-C" is for C-code type of strings
  • "-a" is for ensuring that we get all strings from specified files
  • "-o template.po" defines the output filename.
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.

Go to top

Adding Application to Menu

See Maemo Tutorial 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=maemopad  
(N.B. Whitespace is not allowed after the lines.)
# Service description file
[D-BUS Service]
Name=com.nokia.maemopad
Exec=/usr/bin/maemopad
			

Go to top

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"?>
<ossohelpsource>
  <folder>
    <h2>Help MaemoPad Example</h2>

    <topic>
      <topich2>Main Topic</topich2>
      <context contextUID="Example_MaemoPad_Content" />
      <para>This is a help file with example content.</para>

    </topic>
  </folder>
</ossohelpsource>
      

By using ossohelp_show() function (see osso-helplib.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 MainView struct */
    MainView *mainview = NULL;
    mainview = ( MainView * ) data;
    g_assert(mainview != NULL && mainview->data != NULL );

    retval = ossohelp_show(
      mainview->data->osso, /* osso_context */
      HELP_TOPIC_ID,        /* topic id */
      OSSO_HELP_SHOW_DIALOG);
}
      

To get more information, see the Help Framework HOWTO.

Go to top

Packaging Application

A Debian package is an application packed in one file to make installing easy in the Debian-based operating systems, like maemo platform. More information about creating a Debian packages can be found in the Creating a Debian package. 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 Making Package for Application Manager.

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
maemopad.install
rules
			

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. The 'maemopad.install' defines the localization file used in MaemoPad. 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 version. 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/tmp/<installation directory>

$(MAKE) install DESTDIR=$(CURDIR)/debian/tmp/<installation directory>
			

These lines define where the package files will be installed. Debian/tmp directory is used as a temporary directory for package construction.

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:

maemopad_2.1.dsc

maemopad_2.1 .tar.gz

maemopad_2.1_i386.changes

maemopad_2.1_i386.deb

There is now a .deb file. This package can be installed using "fakeroot dpkg -i maemopad_2.1_i386.deb" command. 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".

Go to top

Downloads

Full source code for MaemoPad can be downloaded from Subversion

Go to top

References

[1] Maemo 4.0 Tutorial

[2] http://www.gnu.org/software/autoconf

[3] http://www.gnu.org/software/automake

[4] GTK+ Reference Manual

[5] Creating a Debian package



Improve this page