Documentation/Maemo 5 Developer Guide/Porting Software/Porting Existing GTK+ Application to Maemo 5

(Scores Saving)
(Integration to Menu)
Line 395: Line 395:
Application integration to menu needs .desktop file. This section describes the needed additions and changes.
Application integration to menu needs .desktop file. This section describes the needed additions and changes.
-
Sliders doesn't have appropriate .desktop file, so we need to generate it from `maemo-sliders.desktop.in' with `configure' script. maemo-sliders.desktop.in
+
Sliders doesn't have appropriate .desktop file, so we need to generate it from 'maemo-sliders.desktop.in' with 'configure' script. maemo-sliders.desktop.in
  <code>[Desktop Entry]
  <code>[Desktop Entry]

Revision as of 06:44, 26 August 2009

Contents

Porting Existing GTK+ Application to Maemo 5.0

This section describes key aspects of the process of porting an application to the maemo platform. When starting to port an application to maemo platform, the first step is to set up the development environment. The actual porting after that is described in this section.

Introduction

Application that is used as an example for porting is Sliders, a GTK+ based game.

The Sliders interface consists of the main window with board, menu and a couple of dialogs. See source mentioned above for game history and description.

Autotools Usage

Sliders doesn't use GNU autotools originally, so we need to add `configure.ac', `Makefile.am' and `autogen.sh' for simplicity. See GNU Build System for corresponding information.

User Interface Changes

Sliders game is not designed for usage in mobile devices, so we need to hildonize it in order to build effective touch interface for this simple game.

Hildonizing Main View

Before using Hildon we need to initialize it. maemo-sliders/sliders.c

  /* Initialize the GTK+ and hildon libraries */
  hildon_gtk_init (&argc, &argv);

After that we can create main window with all necessary content. maemo-sliders/sliders.c

static GtkWidget*
create_main_window (void)
{
  GtkWidget *main_window;
  /* Create the main window */
  main_window = hildon_stackable_window_new ();
  gtk_window_set_title (GTK_WINDOW (main_window), "Welcome to Sliders");
  /* Create and set application menu */
  HildonAppMenu *menu = create_menu ();
  hildon_stackable_window_set_main_menu (HILDON_STACKABLE_WINDOW (main_window), menu);
  /* Create and pack table, that contains sliders */
  appdata.table = create_table ();
  gtk_container_add (GTK_CONTAINER (main_window), appdata.table);
  /* Exit from gtk_main on "delete_event" */
  g_signal_connect ((gpointer) main_window, "delete_event", G_CALLBACK (gtk_main_quit), NULL);
  return main_window;
}

We have replaced original Sliders menu and toolbar with hildon application menu.

Figure 16.12: Maemo Sliders Menu

Image maemo-sliders_app_menu

maemo-sliders/sliders.c

static HildonAppMenu *
create_menu (void)
{
  /*
   * Create menu buttons one by one, connect to appropriate callbacks and
   * add to the menu.
   */
  HildonSizeType button_size = HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH;
  HildonAppMenu *menu = HILDON_APP_MENU (hildon_app_menu_new ());
  GtkButton *button;
  button = GTK_BUTTON (hildon_gtk_button_new (button_size));
  gtk_button_set_label (button, "New");
  g_signal_connect (G_OBJECT (button), "clicked",
      G_CALLBACK (on_new_clicked), NULL);
  hildon_app_menu_append (menu, GTK_BUTTON (button));
  button = GTK_BUTTON (hildon_gtk_button_new (button_size));
  gtk_button_set_label (button, "Scores");
  g_signal_connect (G_OBJECT (button), "clicked",
      G_CALLBACK (on_scores_clicked), NULL);
  hildon_app_menu_append (menu, GTK_BUTTON (button));
  button = GTK_BUTTON (hildon_gtk_button_new (button_size));
  gtk_button_set_label (button, "Open");
  g_signal_connect (G_OBJECT (button), "clicked",
      G_CALLBACK (on_open_clicked), NULL);
  hildon_app_menu_append (menu, GTK_BUTTON (button));
  button = GTK_BUTTON (hildon_gtk_button_new (button_size));
  gtk_button_set_label (button, "Save");
  g_signal_connect (G_OBJECT (button), "clicked",
      G_CALLBACK (on_save_clicked), NULL);
  hildon_app_menu_append (menu, GTK_BUTTON (button));
  button = GTK_BUTTON (hildon_gtk_button_new (button_size));
  gtk_button_set_label (button, "About");
  g_signal_connect (G_OBJECT (button), "clicked",
      G_CALLBACK (on_about_clicked), NULL);
  hildon_app_menu_append (menu, GTK_BUTTON (button));
  return menu;
}


Sliders table buttons (see figure 16.14) are maximized and wrapped with 'hildon_gtk_button_new ()'. maemo-sliders/sliders.c

static GtkWidget*
create_table (void)
{
  GtkWidget *table;
  gint x, y, c = 0;
  appdata.vacant_pos = SLIDERS_NUMBER;
  appdata.move_no = 0;
  HildonSizeType button_size = HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH;
  /* Create table for sliders */
  table = gtk_table_new (4, 4, FALSE);
  /*
   * Create buttons, that represents sliders. Set "current_pos" key for each button
   * and connect each button to appropriate callback.
   */
  for (x = 0; x < SLIDERS_NUMBER; x++)
  {
    gchar *temp = g_strdup_printf ("%i", x+1);
    appdata.buttons[x] = hildon_gtk_button_new (button_size);
    gtk_button_set_label (GTK_BUTTON(appdata.buttons[x]), temp);
    g_object_set_data (G_OBJECT(appdata.buttons[x]), "current_pos", (gpointer) x);
    g_free (temp);
    gtk_signal_connect(GTK_OBJECT(appdata.buttons[x]), "clicked", GTK_SIGNAL_FUNC(on_button_clicked), NULL);
  }
  /* Add sliders to table */
  for (x = 0; x < 4; x++)
  for (y = 0; y < 4; y++)
    if (!(x == 3 && y == 3))
      gtk_table_attach_defaults (GTK_TABLE (table), appdata.buttons[c++], y, y+1, x, x+1);
  return table;
}


Figure 16.13: Maemo Sliders Table

Image maemo-sliders_table

Implementing Scores Window

We have replaced original Sliders win picture by scores window (figure 16.14), that appears in two cases. It's shown after ``Scores menu button clicked maemo-sliders/sliders.c

static void
on_scores_clicked (GtkMenuItem *menuitem, gpointer user_data)
{
  scores_window (0);
}

and when player has won. maemo-sliders/sliders.c

static void
on_button_clicked (GtkWidget *button, gpointer user_data)
{
  /* ... */
    if ( has_player_won() )
    {
      scores_window (1);
      gtk_window_set_title (GTK_WINDOW (appdata.main_window), "Welcome to Sliders");
      appdata.move_no = 0;
    }
    else
    {
      gchar *temp = g_strdup_printf ("Move no.: %i", appdata.move_no);
      gtk_window_set_title (GTK_WINDOW (appdata.main_window), temp);
      g_free (temp);
    }
  /* ... */
}


Figure 16.14: Maemo Sliders Scores

Image maemo-sliders_scores_window


Scores window contains pannable area with tree view maemo-sliders/sliders.c

  /* Create a tree view that represents scores table and an area for it */
  area = hildon_pannable_area_new ();
  tree_view = create_treeview (HILDON_UI_MODE_EDIT);
  /* ... */
  /* Pack tree view */
  gtk_container_add (GTK_CONTAINER (area), tree_view);
  gtk_container_add (GTK_CONTAINER (window), area);

and edit toolbar. maemo-sliders/sliders.c

  /* Create a new edit toolbar */
  toolbar = hildon_edit_toolbar_new_with_text ("Choose scores to delete",
                                               "Delete");
  /* ... */
  /* Add toolbar to the window */
  hildon_window_set_edit_toolbar (HILDON_WINDOW (window),
                                  HILDON_EDIT_TOOLBAR (toolbar));
  /* ... */
  /* Set callback for "Delete" button */
  g_signal_connect (toolbar, "button-clicked",
                    G_CALLBACK (delete_selected_scores),
                    tree_view);
  /* Destroy scores window when upper corner arrow clicked */
  g_signal_connect_swapped (toolbar, "arrow-clicked",
                            G_CALLBACK (gtk_widget_destroy),
                            window);

Win banner is shown if 'scores_window ()' called in case player has won. maemo-sliders/sliders.c

  /* Additional actions for case, when player has won */
  if (win)
  {
    /* Show appropriate banner */
    hildon_banner_show_informationf (window, NULL, "Win! Score: %i", appdata.move_no);
    /* ... */
  }

Other scores related code is quite straightforward and doesn't use Hildon specific features, see 'scores_window ()' implementation for details.

State Saving

Maemo supports state saving feature, that we have used for current Sliders game saving. The application can later open this game with the same state as before. This section describes most important steps needed to make Sliders support state savings.

'state_save ()' is called when user presses menu's "Save" button. Saving data should be updated before it's called. maemo-sliders/sliders.c

static void
on_save_clicked (GtkMenuItem *menuitem, gpointer user_data)
{
  /* Save moves number, vacant position and board layout to "state" */
  state.move_no = appdata.move_no;
  state.vacant_pos = appdata.vacant_pos;
  int i;
  for (i = 0; i < SLIDERS_NUMBER; i++)
    state.map[i] = (gint) gtk_object_get_data(GTK_OBJECT(appdata.buttons[i]), "current_pos");
  /* Call state saving method and process return value */
  if (state_save ())
    hildon_banner_show_information (appdata.main_window, NULL, "Saved current game");
  else hildon_banner_show_information (appdata.main_window, NULL, "Can't save current game");
}

Then 'osso_state_write ()' can be called for correct data. maemo-sliders/sliders.c

state_save(void)
{
  osso_state_t osso_state;
  osso_return_t ret;
  osso_state.state_size = sizeof(StateData);
  osso_state.state_data = &state;
  ret = osso_state_write(appdata.osso_context, &osso_state);
  if (ret != OSSO_OK)
    return FALSE;
  return TRUE;
}

'state_load ()' is called when user clicks "Open" menu button. It uses 'osso_state_read ()' symmetric to `state_save ()'. maemo-sliders/sliders.c

state_load(void)
{
  osso_state_t osso_state;
  osso_return_t ret;
  osso_state.state_size = sizeof(StateData);
  osso_state.state_data = &state;
  ret = osso_state_read(appdata.osso_context, &osso_state);
  if (ret != OSSO_OK)
    return FALSE;
  return TRUE;
}

Don't forget to initialize the library context before state saving usage. maemo-sliders/sliders.c

  /* Initialize maemo application */
  appdata.osso_context = osso_initialize(OSSO_SERVICE, VERSION, TRUE, NULL);

The information saved by using state saving functions does not survive over power off of the device, so if you need to save some data more permanent you could use e.g. libxml as shown in next section for scores table.

Scores Saving

Scores data store is created, when 'get_scores_store ()' called first. The store is populated from file saved under user's home directory. Document tree is parsed with node pointer starting from root element. maemo-sliders/sliders.c

static GtkListStore *
get_scores_store (void)
{
  static GtkListStore *store = NULL;

  /* Return store if it has been created before */
  if (store != NULL)
    return store;

  /* Create store with 2 columns of specified types */
  store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_INT);

  xmlDocPtr doc; /* the resulting document tree */
  xmlNodePtr cur; /* node pointer */
  xmlChar *attribute; /* node attribute name */
  char *row_name, *temp;
  gint row_score;

  /* Parse an XML file from the filesystem */
  char *slidersdir = g_strdup_printf ("%s%s%s", getenv("HOME"), SLIDERS_DIR, SCORES_XML);
  doc = xmlReadFile (slidersdir, NULL, XML_PARSE_NOERROR);
  g_free (slidersdir);
  if (doc == NULL) {
    g_print ("%s, %i: Failed to parse document %s\n", __PRETTY_FUNCTION__, __LINE__, SCORES_XML);
    return store;
  }

  /* Parse root element */
  cur = xmlDocGetRootElement(doc); //scores
  if (cur == NULL) {
    g_print ("%s, %i: Empty xml doc\n", __PRETTY_FUNCTION__, __LINE__);
    xmlFreeDoc (doc);
    return store;
  }

  /* Parse first row tag */
  cur = cur->xmlChildrenNode;
  if (cur == NULL) {
    g_print ("%s, %i: No row tag\n", __PRETTY_FUNCTION__, __LINE__);
    xmlFreeDoc (doc);
    return store;
  }

  /* Parse row tags one by one */
  while (cur != NULL)
  {
    /* ... */

    /* Add new row to the store */
    gtk_list_store_insert_with_values (store, NULL, gtk_tree_model_iter_n_children (GTK_TREE_MODEL(store), NULL), 0, row_name, 1, row_score, -1);

    /* Parse next row element */
    g_free (row_name);
    cur = cur->next;
  }

  /* Free the resulting tree */
  xmlFreeDoc(doc);
  return store;
}

'scores_xml_save ()' is called before application quits. It uses xml text writer in order to save current data store to appropriate file. maemo-sliders/sliders.c

static void
scores_xml_save (const char *uri)
{
  int rc;
  xmlTextWriterPtr writer;

  /* Check if store have been created */
  GtkListStore *store = get_scores_store ();
  if (!store) return;

  /* Create a new XmlWriter for uri, with no compression. */
  writer = xmlNewTextWriterFilename(uri, 0);
  if (writer == NULL) {
    g_print ("%s, %i: Error creating the xml writer for %s\n", __PRETTY_FUNCTION__, __LINE__, SCORES_XML);
    return;
  }

  /* Start the document with the xml default for the version,
   * encoding UTF-8 and the default for the standalone
   * declaration. */
  rc = xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL);
  if (rc < 0) {
    g_print ("%s, %i: Error at xmlTextWriterStartDocument\n", __PRETTY_FUNCTION__, __LINE__);
    return;
  }

  /* Start an element named ROOT_ELEMENT. Since thist is the first
   * element, this will be the root element of the document. */
  rc = xmlTextWriterStartElement(writer, BAD_CAST ROOT_ELEMENT);
  if (rc < 0) {
    g_print ("%s, %i: Error at xmlTextWriterStartElement\n", __PRETTY_FUNCTION__, __LINE__);
    return;
  }

  /* Save each score row from store. */
  gtk_tree_model_foreach (GTK_TREE_MODEL(store), scores_xml_save_score, &writer);

  /* Here we could close the element named ROOT_ELEMENT using the
   * function xmlTextWriterEndElement, but since we do not want to
   * write any other elements, we simply call xmlTextWriterEndDocument,
   * which will do all the work. */
  rc = xmlTextWriterEndDocument(writer);
  if (rc < 0) {
    g_print ("%s, %i: Error at xmlTextWriterEndDocument\n", __PRETTY_FUNCTION__, __LINE__);
  }

  xmlFreeTextWriter(writer);
}

Integration to Menu

Application integration to menu needs .desktop file. This section describes the needed additions and changes.

Sliders doesn't have appropriate .desktop file, so we need to generate it from 'maemo-sliders.desktop.in' with 'configure' script. maemo-sliders.desktop.in

[Desktop Entry]
Encoding=UTF-8
Version=@VERSION@
Type=Application
Name=Maemo Sliders
Exec=@prefix@/bin/maemo-sliders
Icon=hello

Application Packaging

`dh_make' tool have been used for Sliders debianization