How to write a new application to maemo 3.x

This document is released under GPL license.

Introduction

This document has been reviewed for maemo 3.x.

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 we use a simple plain text editor with only a few essential features. Good examples for this kind of plain text editors are gtk2edit and gpe-edit. From now on we call our application as an "MaemoPad".

MaemoPad will have some basic features, such as: "New", "Open", "Save", "Save As...", "Cut", "Copy", "Paste", "Font", "Full Screen" and "Close". For the simplicity there will be no features like "Undo", "Redo", different fonts in the same document, pictures, tables etc.

Graphic6

Figure 1. MaemoPad application

The Figure 1 is a screenshot from the MaemoPad. As you can see MaemoPad application has it's own toolbar at bottom of screen and a drop down menu in left upper corner. The left space is used to show the file.

Creating Application File Structure

First we have to create the file structure for Maemopad. The structure presented is quite genaral and can be used for different kind of applications on the maemo SDK platform.

The project directory has four subdirectories: src/, po/, debian/, and data/. Source files are located in the src/ directory, which contains ui/ subdirectory for UI related code. Localization files are located in the po/ directory.

Files to create Debian package are in the debian/ directory. Debian packaging is explained in details later. The data subdirectory has files to add MaemoPad to the maemo application menu, application icons, and help files.

The project directory includes only three script files to configure and compile the project: autogen.sh, configure.ac, and Makefile.am. The autogen.sh file is a tiny script file which uses GNU-toolchain to make other script files to build the project. Configure.ac includes definitions on how to produce the configure script for our project.

The Makefile.am file in the main project directory include the subdirectories src/, po/ and data/ and Makefiles in src/, po/, and data/ directories include the files and subdirectories under them needed to make the application. The GNU-toolchain is out of the scope of this document and you should study references [2] and [3] if you want to learn more.

In the src/ directory main.c is in the top of the hierarchy in the MaemoPad's 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 chapter. The file interface.c in the src/ui/ directory takes care of creating the graphical user interface. Callbacks.c contains the code that follows the messages between MaemoPad and maemo platform, read more in the functionality chapter.

The localization directory po/ has a file POTFILES.in, which lists 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.

The file structure of the MaemoPad application will look 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
	maemopad.links
	rules
	

Coding Application Main

This chapter explains how to make files main.c and appdata.h. Main.c has the code for initializing and setting up everything for the application, starting the applications, and freeing the used memory and exiting in the end. Appdata.h has the data needed by the application: pointers, state bits etc. We are making a Hildon based application and it is made by using HildonProgram [1]. Main.c uses functions interface_main_view_new() and interface_main_view_destroy() of interface.c to create a view for the application and to destroy the view when closing application.

Main.c

Here's the main function with comments:

int main( int argc, char* argv[] )
{
    AppData* data;
    HildonProgram* program;
    MainView* main_view;

    /* Initialise 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;
}	  
		

Appdata.h

Appdata.h defines AppData structure, which contains the basic information of the MaemoPad application. The AppData structure is defined in the appdata.h file:

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

User Interface

The source file interface.c includes all code which make the the graphical user interface (GUI) work. GTK and Hildon API are used to create the GUI. At first our application interface needs a view, which is implemented using HildonWindow [1]. There is drop down menu, toolbar, and scrollable text area in the application view. All of these are made using standard GTK components. Hildon adds some features to GTK, for example theming and differently formed dialogs, though.

Hildon applications have a predefined place for drop down menu and toolbar so we attach our application menu to Hildon application menu and our toolbar to Hildon application toolbar. Rest of the view is dedicated for the scrollable text area. The figure 2 shows how the components will be placed.

Graphic5

Illustration 2. HildonWindow's components

Our interface.c has two public functions: main_view_new and main_view_destroy. Main_view_new creates a view for the application given by parameter and returns a handle to that view. Main_view_destroy destroys the view given as a parameter. Main_view_new uses private functions create_menu to create a drop down menu, create_toolbar to create a tool bar and create_textarea to create a scrollable text view. We use struct MainView to store view data.

To get more information about Hildon study the Maemo Tutorial [1]. If you are not familiar with GTK, a good reference is http://developer.gnome.org/doc/API/2.0/gtk/index.html [4].

Interface.h

In the interface header file interface.h we define public functions for main.c and callbacks.c, confirmation responses for the save changes note, MaemopadError enum for the Hildon error note, the MainView struct for the state of our interface (file name under editing, used screen font, fullscreen on or off etc.), and a list of GTK components used by menu, toolbar and textarea. The file interface.h looks like this:

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

#define _(String) gettext(String)

/* confirmation responses */
#define CONFRESP_YES 1
#define CONFRESP_NO 2
#define CONFRESP_OK 3
#define CONFRESP_CANCEL 4
#define CONFRESP_ERROR 5

#define MAIN_VIEW_NAME "MainView"

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 _MainView MainView;
struct _MainView
{
    /* 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;
	. . .

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

};

/* Publics: */
MainView* interface_main_view_new( AppData* data );
void interface_main_view_destroy( MainView* main );
char* interface_file_chooser( MainView* main, GtkFileChooserAction action );
PangoFontDescription* interface_font_chooser( MainView * main );
gint interface_save_changes_note( MainView* main );
void interface_error( MaemopadError me, MainView *main );

		

Creating GUI

The GUI initialization needs a lot of code so it is good to explain it in smaller parts. Our interface.c file has private functions to create menu, toolbar and textarea. Destroying is done with a simple command, which just frees used memory.

Create Menu

For the menu we have to attach our menu to the Hildon application view, create menu items, attach these menu items to the right submenus, and connect callback signals to menu items.

static void create_menu( MainView *main )
{
    /* Create needed handles */
    GtkMenu *main_menu;
    GtkWidget *file_menu, *edit_menu;
    GtkWidget *separator = NULL;
    GtkWidget *close = NULL;

    /* Create main menu and new menus for submenus in our drop down menu */
    main_menu = GTK_MENU( gtk_menu_new () );
    file_menu = gtk_menu_new ();
    edit_menu = gtk_menu_new ();

    /* Create the menu items */
    main->file_item = gtk_menu_item_new_with_label ("File");
    main->new_item = gtk_menu_item_new_with_label ("New");
	. . .

    /* Add menu items to right menus */
    gtk_menu_append( main_menu, main->file_item );
    gtk_menu_append( file_menu, main->new_item );
	. . .

    /* Add submenus to the right items */
    gtk_menu_item_set_submenu( GTK_MENU_ITEM (main->file_item), file_menu );
    gtk_menu_item_set_submenu( GTK_MENU_ITEM (main->edit_item), edit_menu );

    /* Attach the callback functions to the activate signal */
    g_signal_connect( G_OBJECT( main->new_item ), "activate", 
		      G_CALLBACK ( callback_file_new), main );
    g_signal_connect( G_OBJECT( main->open_item ), "activate", 
		      G_CALLBACK ( callback_file_open), main );
	. . . 

    /* We need to show menu items */
    gtk_widget_show_all( GTK_WIDGET( main_menu ) );
}		
			

Create Toolbar

The toolbar is implemented following the same principle as in the previous menu chapther. First we create buttons, then we add them to the toolbar, and after that connect callback signals to them. Finally we add the toolbar to our application main view.

static void create_toolbar ( MainView *main ) 
{
    /* Create new GTK toolbar */
    main->toolbar = gtk_toolbar_new ();

    /* Set toolbar properties */
    gtk_toolbar_set_orientation( GTK_TOOLBAR(main->toolbar), GTK_ORIENTATION_HORIZONTAL);
    gtk_toolbar_set_style( GTK_TOOLBAR(main->toolbar), GTK_TOOLBAR_BOTH_HORIZ);

    /* Make menus and buttons to toolbar: */
    /* Create toolitems using defined items from stock */
    main->new_tb = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
    main->open_tb = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN);
  	. . .

    /* Insert items to toolbar */
    gtk_toolbar_insert
	( GTK_TOOLBAR(main->toolbar), main->new_tb, -1);
    gtk_toolbar_insert
	( GTK_TOOLBAR(main->toolbar), main->open_tb, -1);
	. . .

    /* Connect signals to buttons */
    g_signal_connect(G_OBJECT(main->new_tb), "clicked",
                     G_CALLBACK(callback_file_new), main);
    g_signal_connect(G_OBJECT(main->open_tb), "clicked",
                     G_CALLBACK(callback_file_open), main);
	. . .
	
    /* Add toolbar to the HildonWindow */
    hildon_window_add_toolbar(main->data->window, GTK_TOOLBAR(main->toolbar));

    /* Show toolbar */
    gtk_widget_show_all (GTK_WIDGET(main->toolbar));
    gtk_widget_show_all (GTK_WIDGET(main->data->window) );
}					
			

Create Textarea

The textarea is used to show the file under editing. Our textarea includes scrolled_window with text_view. We use scrolled_window because many files have often more text than we can show on the screen at once. We connect our main->buffer to text_view and callback signals for detecting text editing.


void create_textarea( MainView *main )
{
    /* Create scrolled window */
    main->scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
    gtk_widget_show(main->scrolledwindow);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(
        main->scrolledwindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

    /* Text view */
    main->textview = gtk_text_view_new ();

    /* Some text view settings */
    gtk_text_view_set_editable (GTK_TEXT_VIEW (main->textview), TRUE);
    gtk_text_view_set_left_margin (GTK_TEXT_VIEW (main->textview), 10);
    gtk_text_view_set_right_margin (GTK_TEXT_VIEW (main->textview), 10);

    /* Get handle to buffer */
    main->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW
	(main->textview));

    /* Enable Rich Text Support */
    gtk_text_buffer_set_can_paste_rich_text ( main->buffer, TRUE );
    gtk_text_buffer_set_rich_text_format ( main->buffer, "RTF" );

    /* Put textview under scrolledwindow and show it*/
    gtk_container_add(GTK_CONTAINER(main->scrolledwindow), main->textview);
    gtk_widget_show(main->textview);

    /* Change default font throughout the widget */
    main->font_desc = pango_font_description_from_string (DEFAULT_FONT);
    gtk_widget_modify_font (main->textview, main->font_desc);

    /* Connect signals to detect changes in text */
    g_signal_connect (G_OBJECT (main->buffer), "modified-changed",
                      G_CALLBACK(callback_buffer_modified), main);
    g_signal_connect (G_OBJECT (main->buffer), "changed",
                      G_CALLBACK(callback_buffer_modified), main);
}					
			

File and Font Chooser Dialogs

Hildon dialogs are used to choose filename and font. Hildon dialogs are almost the same as GTK dialogs, but there are little differences on how they work.

File Chooser

To choose files in open and save functions we need to create Hildon dialog which handles file choosing. Our file chooser function creates dialog and returns selected file's directory and name if OK response is received.

gchar* interface_file_chooser
(MainView * mainview, GtkFileChooserAction action)
{
    GtkWidget *dialog;
    gchar* filename = NULL;

    dialog = hildon_file_chooser_dialog_new(
	GTK_WINDOW(mainview->data->program), 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;
}			
			

Font Chooser

The font callback needs a dialog to choose the font from. For this we use a Hildon font selection dialog. We copy the settings from the dialog and set the font accordingly.

PangoFontDescription* interface_font_chooser( MainView * main )
{
    HildonFontSelectionDialog *dialog = NULL;

    PangoFontDescription *font = NULL;
    gint size;
    gboolean bold, italic;
    gchar *family = NULL;

    font = pango_font_description_new();

    /* create dialog */
    dialog = HILDON_FONT_SELECTION_DIALOG ( hildon_font_selection_dialog_new( NULL, NULL ) );

    gtk_widget_show_all (GTK_WIDGET(dialog));
    if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
    {
        g_object_get(G_OBJECT(dialog),
                    "family", &family,
                    "size", &size,
                    "bold", &bold,
                    "italic", &italic,
                    NULL);
        pango_font_description_set_family(font, family);
        pango_font_description_set_size(font, size * PANGO_SCALE);
        if (bold) {
            pango_font_description_set_weight(font, PANGO_WEIGHT_BOLD);
        } else {
            pango_font_description_set_weight(font, PANGO_WEIGHT_NORMAL);
        }
        if (italic) {
            pango_font_description_set_style(font, PANGO_STYLE_ITALIC);
        } else {
            pango_font_description_set_style(font, PANGO_STYLE_NORMAL);
        }
    }
    gtk_widget_destroy(GTK_WIDGET(dialog));

    return font;
}           
			

Save and Error Dialogs

A couple of different dialogs are also needed to show messages and receive feedback from the MaemoPad user. These are save changes confirmation dialog and error dialog. Both of these are made using Hildon notes.

Save Changes

The Save Changes note is made to ensure that the user don't lose edited before creating a new file or opening an existing file. We use hildonNote for this. Responses are defined in interface.h.

gint interface_save_changes_note(MainView * main)
{
    HildonNote *hn = NULL;
    gint response = FALSE;

    g_assert(main != NULL && main->data->program != NULL);

    hn = HILDON_NOTE(hildon_note_new_confirmation_add_buttons
                     (GTK_WINDOW(main->data->window),
                      _("maemopad_save_changes_made"),
                      _("maemopad_yes"), CONFRESP_YES,
                      _("maemopad_no"), CONFRESP_NO,
                      NULL, NULL));
    response = gtk_dialog_run(GTK_DIALOG(hn));
    gtk_widget_destroy(GTK_WIDGET(hn));
    return response;
}			
			

Error

We need to inform user if something went wrong, too, in MaemoPad about failing to save or open a file. We use hildon_note_new_information for this. MAEMOPAD_ERRORs are defined in interface.h.

void interface_error( MaemopadError me, MainView *main )
{
    GtkWidget *dialog = NULL;
    gchar *msg = NULL;

    switch (me) {

    case MAEMOPAD_ERROR_INVALID_URI:
        msg = g_strdup(_("maemopad_ms_invalid_uri"));
        break;
        . . .
    default:
        g_assert_not_reached();
    }

    dialog = hildon_note_new_information
	( GTK_WINDOW( main->data->program ), msg );

    gtk_dialog_run( GTK_DIALOG( dialog ) );
    gtk_widget_destroy( dialog );
    g_free( msg );
}					
			

Functionality

Edit Callbacks

Edit callbacks are quite simple to use. In every edit function, we need to connect parameter pointer data to our MainView struct. Then we use standard GTK functions to cut, copy and paste to our buffer. mainview->clipboard is used to store text from commands cut and copy and we get the same text from clipboard when using command paste.

Cut

Cut is implemented using a simple GTK command. It copies selected text and then deletes it if the last parameter is TRUE.

void callback_edit_cut( GtkAction * action, gpointer data )
{
        . . .
    /* do cut */
    gtk_text_buffer_cut_clipboard( GTK_TEXT_BUFFER(mainview->buffer),
	mainview->clipboard, TRUE);
}					
			

Copy

The GTK function for copy only needs parameters for the buffer and the clipboard used.

void callback_edit_copy( GtkAction * action, gpointer data )
{
        . . .
    /* do copy */
    gtk_text_buffer_copy_clipboard( GTK_TEXT_BUFFER(mainview->buffer),
	mainview->clipboard);
}
			

Paste

When pasting the second latest parameter is set to NULL to paste is to the cursor position, as we want. The last parameter defines if the buffer is editable or not.

void callback_edit_paste( GtkAction * action, gpointer data )
{
	. . .
    gtk_text_buffer_paste_clipboard( GTK_TEXT_BUFFER( mainview->buffer ),
	mainview->clipboard, NULL, TRUE );
}
			

File Callbacks

In the file callback we need to verify from the user if she wants to save unsaved changes. For this we use file I/O functions.

New

When the new file callback is used, we need to ensure that changes are saved if the user wants so. In the case user wants to save the content and we don't have a file name, we have to choose the name for the file, too.

void callback_file_new(GtkAction * action, gpointer data)
{
        . . .
    /* 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;
	  

Open

In the open file callback the opening dialog appears to select the right file.

void callback_file_open(GtkAction * action, gpointer data)
{
	. . .
    /* save changes note if file is edited */
    if( mainview->file_edited ) {
        answer = interface_save_changes_note( mainview );
        if( answer == CONFRESP_YES ) {
            /* check if we had a new file */
            if( mainview->file_name == NULL ) {
                mainview->file_name = interface_file_chooser ( mainview, GTK_FILE_CHOOSER_ACTION_SAVE );
            }
            write_buffer_to_file ( mainview );
        }
    }

    /* open new file */
    filename = interface_file_chooser ( mainview, GTK_FILE_CHOOSER_ACTION_OPEN );

    /* if we got a file name from chooser -> open file */
    if( filename != NULL ) {
        mainview->file_name = filename;
        read_file_to_buffer ( mainview );
        mainview->file_edited = FALSE;
    }
}
			

Save

The save file callback saves the file. If there is no filename defined, we have to choose it first with a file chooser.

void callback_file_save(GtkAction * action, gpointer data)
{
	. . .
    /* check if we had a new file */
    if( mainview->file_name != NULL ) {
        write_buffer_to_file ( mainview );
    } else { 
        filename = interface_file_chooser
	( mainview, GTK_FILE_CHOOSER_ACTION_SAVE );
        /* if we got a file name from chooser -> save file */
        if( filename != NULL ) {
            mainview->file_name = filename;
            write_buffer_to_file ( mainview );
            mainview->file_edited = FALSE;
        }
    }
}		
			

Save As...

Save as... chooses first a filename with a file chooser, then saves the file.

void callback_file_saveas(GtkAction * action, gpointer data)
{
	. . .

    filename = interface_file_chooser
	( mainview, GTK_FILE_CHOOSER_ACTION_SAVE );

    /* if we got a file name from chooser -> save file */
    if( filename != NULL ) {
        mainview->file_name = filename;
        write_buffer_to_file ( mainview );
        mainview->file_edited = FALSE;
    }
}
			

Font Callback

In the font callback we use interface's function interface_font_chooser to get a new font. Then we just modify the screen font by using a GTK function gtk_widget_modify_font.


void callback_font( GtkAction * action, gpointer data )
{
	. . .
    new_font = interface_font_chooser( mainview );

    /* if we got a new font from chooser -> change font */
    if( new_font != NULL ) {
        mainview->font_desc = new_font;
        gtk_widget_modify_font( mainview->textview, mainview->font_desc );
    }    
}	  
			

Fullscreen Callback

The fullscreen callback toggles full screen mode on and off. This is easy to implement in a Hildon application.

void callback_fullscreen( GtkAction * action, gpointer data )
{
	. . .
    /* toggle fullscreen on<->off */
    mainview->fullscreen = !mainview->fullscreen;
    if (mainview->fullscreen) {
        gtk_window_fullscreen(GTK_WINDOW(mainview->data->window));
    } else {
        gtk_window_unfullscreen(GTK_WINDOW(mainview->data->window));
    }
}
			

Buffer Modified Callback

The callback for buffer modifications updates the MaemoPad text buffer to have the edited state flag, so that in the case the user wants to open or create a new file, we need to ask a confirmation.

void callback_buffer_modified ( GtkAction * action, gpointer data )
{
	. . .
    /* change state */
    mainview->file_edited = TRUE;
}
			

File I/O functions

Maemopad needs to access files when opening or saving text under editing. Maemo uses GnomeVFS for file operations. You can get more information about GnomeVFS at http://developer.gnome.org/doc/API/gnome-vfs/ [5]. We need read and write functions for our file callbacks.

Reading From File

Reading files is implemented with GnomeVFS functions. First we get the file information and create a handle to the file. Then we open the file and read it to the text buffer. When the data has been read, the file is closed.

void read_file_to_buffer ( MainView* mainview )
{
	. . .
    /* try to get file info */
    vfs_result = gnome_vfs_get_file_info
	(mainview->file_name, &finfo, GNOME_VFS_FILE_INFO_DEFAULT);
    if ( vfs_result != GNOME_VFS_OK ) {
        interface_error( MAEMOPAD_ERROR_OPEN_FAILED, mainview );
        return;
    }
    /* try to create handle to file */
    vfs_result = gnome_vfs_open
	(&handle, mainview->file_name, GNOME_VFS_OPEN_READ);
    if ( vfs_result != GNOME_VFS_OK ) {
        interface_error( MAEMOPAD_ERROR_OPEN_FAILED, mainview );
        return;
    }

    /* allocate memory for temp_buffer */
    temp_buffer = g_malloc(finfo.size + 1);
    memset(temp_buffer, 0, finfo.size + 1);
    
    /* read from file to buffer */
    gnome_vfs_read(handle, temp_buffer, finfo.size, &in_bytes);

    /* set text to screen */
    gtk_text_buffer_set_text
	( GTK_TEXT_BUFFER (mainview->buffer), temp_buffer, -1);

    /* free temp, close file and return */
    g_free(temp_buffer);
    gnome_vfs_close(handle);
}		
			

Writing To File

Writing to a file is also done with GnomeVFS functions. First we have to create a handle to the file. Then we copy the text from MaemoPad to temp_buffer for saving and write temp_buffer to the file. Finally we close the handle.

void write_buffer_to_file ( MainView* mainview )
{
	. . .
    /* try to create handle to file */
    vfs_result = gnome_vfs_create
	(&handle, mainview->file_name, GNOME_VFS_OPEN_WRITE, 0, 0600);
    if ( vfs_result != GNOME_VFS_OK ) {
        interface_error( MAEMOPAD_ERROR_SAVE_FAILED, mainview );
        return;
    }

    /* find start and end of text */
    gtk_text_buffer_get_bounds
	( GTK_TEXT_BUFFER (mainview->buffer), &start, &end);

    /* copy all text from screen to temp_buffer */
    temp_buffer = gtk_text_buffer_get_slice
	( GTK_TEXT_BUFFER (mainview->buffer), &start, &end, TRUE);

    /* write text to file */
    gnome_vfs_write(handle, temp_buffer, strlen(temp_buffer), &out_bytes);

    /* free temp, close file and return */
    g_free(temp_buffer);
    gnome_vfs_close(handle);
}
			

Localization

Localization means translating the application to different languages. Localization needs couple of files which are stored in the po/ directory. The following files will be used for localization:

Makefile.am	
POTFILES.in
en_GB.po
			

POTFILES.in contains the list of source code files which will be localized. File en_GB.po includes translated text.

Creating en_GB.po

Creating po files is quite simple. In MaemoPad we make only one localization file en_GB.po, others like fi_FI.po can be easily made based on this. The Localization file structure is simple, you have to define only localization id and actual text string. A sample of this structure follows:


#: src/document.c:727 src/document.c:733
msgid "note_ib_autosave_recover_failed"
msgstr "Recovering autosaved file failed"
			

So "msgid" is the original string (key) used in the code and "msgstr" defines the translated string for localization.

First we create a template file including all strings from the source files for translation. We use GNU xgettext command [8] to extract the strings from sources:

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

Option "-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 sources and "-o template.po" defines the output filename.

This may output some warnings which usually are not serious, but it is better to check them anyway.

Then we edit template.po with a text editor and the content should be something like above. When you look at the strings got from the sources you can see that some of them, like signal "clicked", empty strings "", font names etc., are not going to be translated. Those lines should be deleted or commented away. After this we can write translations of British English to each "msgstr" line.

After editing we just save the file with name en_GB.po to the po/ directory and delete the template.po.

Adding Application To Menu

To add application icon into the maemo Task Navigator menu, a couple of files are needed. These are .desktop and .service files.

Creating .desktop file

It is good to ensure that .desktop file uses the same directory structure as the rest of the project. Next we look what our maemopad.desktop looks like:

[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
X-Osso-Type=application/x-executable	  
			

Remark that it is not allowed to have whitespace after the lines.

Creating .service file

A service file is also needed to launch applications from the Task Navigator. The service file in our data/ directory is following:

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

Adding Help

Applications can have their own help files. Help files are XML files that are located under /usr/share/osso-help directory. Each language is in its own subdirectory, like /usr/share/osso-help/en_GB for British English help files. In Maemopad there's data/help/en_GB/MaemoPad.xml, which contains very simple help content. It is mandatory that the middle part of the contextUID middle 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) you can show this help content in your application. You can create a "Help" menu item and connect this kind of callback function 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 check the maemo help framework HOWTO.

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 official Maintainers' quide (http://www.debian.org/doc/maint-guide/) [9]. Our goal in this chapter is to create a Debian package of MaemoPad, which can be installed in the maemo platform.

If you want to create a package, that can be installed using the the Application Manager, see Making a package for the Application Manager.

The next two sections briefly discuss debian package creation. For a more detailed guide see the Creating a Debian package page.

Creating debian/ directory

We need some files to create the package. Thay are placed under the debian/ directory. These files will be created:

changelog
control
copyright
maemopad.install
maemopad.links
rules
			

'rules' file is the file which defines how the Debian package is built. 'rules' file tells where the files will be installed. We need also 'control' file to define what kind of packages (often different language versions) we are going to create. The 'maemopad.links' file defines link to Task Navigator. The 'maemopad.install' defines the localization file used in MaemoPad. Changelog and copyright files are also needed or building the package doesn't work. The 'changelog' defines the version number of the package and short description about changes compared to older version. 'copyright' includes information in plain text about package's 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 package

The package is made by using the following command:

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

The result should be these MaemoPad files:

maemopad_1.5.dsc

maemopad_1.5 .tar.gz

maemopad_1.5_i386.changes

maemopad_1.5_i386.deb

There's a .deb file now. This package can be installed using "fakeroot dpkg -i maemopad_1.1_i386.deb" command. Icon to the application should now be in the maemo Task Navigator menu and you should be able to launch it from there. The package can be removed with the command "fakeroot dpkg -r maemopad".

Code examples

Full source code for for MaemoPad can be downloaded from Downloads section



Improve this page