Preference Sample Overview

Mobile native

The Preference sample application demonstrates how to work with custom preferences stored in an underlying database. The preferences are stored as key-value pairs with a defined value type (integer, double, boolean, or string). You can use the preferences as an application configuration storage, where the key-value pairs remain valid for subsequent launches of the related application.

The following figure illustrates the application view.

Figure: Preference screen

Preference screen

You can:

  • Create a new preference (key, value, type)

    You must provide a custom key name and value type to be stored (integer, double, boolean, or string), and assign a value corresponding to the defined type.

  • List and browse all available preferences

    All defined preferences are displayed using the elm_genlist component. The related value and its type are displayed in a text format.

  • Remove all available preferences

    You can remove all defined preferences with a single batch operation.

  • Remove selected preferences

    Once the available preferences are displayed in a list, you can remove selected preferences using the remove button attached to the list item.

The following figure illustrates the structure of the user interface (EDJE layout scripts are used).

Figure: Preference UI layout and component structure

Preference UI layout structure

Preference UI components structure

The application workflow can be divided into 4 logical blocks:

  • Startup
  • Key-value input
  • Preference deletion
  • Removal of all preferences

Due to the Preference API constraints, the application defines 1 additional preference (PROPERTY_ITEMS_ENUM_KEY), which is used internally. Its purpose is to bind a value type to the key name, as the Preference API does not provide information on the type of the key value.

In order to identify the type of the value assigned to a specified key, the PROPERTY_ITEMS_ENUM_KEY preference is used as a lookup table. The structure of the PROPERTY_ITEMS_ENUM_KEY preference is:

PROPERTY_ITEMS_ENUM_KEY = key_name_1:value_type;key_name_2:value_type; ... ;key_name_N:value_type;

Where

  • key_name_X is the name of the Xth preference defined by the user
  • value_type is the type of the value assigned to the preference:
    • Integer: 0
    • Double: 1
    • Boolean: 2
    • String: 3

The following figure illustrates the application workflow with the PROPERTY_ITEMS_ENUM_KEY constraints.

Figure: Application workflow

Application workflow

Prerequisites

To ensure proper application execution, the following privilege must be set:

  • http://tizen.org/privilege/systemsettings

Implementation

Type Definitions

typedef struct _viewdata 
{
   Evas_Object* win;
   Evas_Object* conform;
   Evas_Object* main_layout;
   Evas_Object* pref_edit_panel_layout;
   Evas_Object* pref_edit_panel_key_entry;
   Evas_Object* pref_edit_panel_value_entry;
   Evas_Object* pref_edit_panel_type_selector;
   Evas_Object* pref_buttons_panel_layout;
   Evas_Object* pref_buttons_panel_left_button;
   Evas_Object* pref_buttons_panel_right_button;
   Evas_Object* pref_list_panel_list;
   Eina_List* genlist_items;
} viewdata_s;
typedef struct _modeldata 
{
   pref_value_type_t selected_pref_value_type;
} modeldata_s;
typedef enum {PREF_INTEGER = 0, PREF_DOUBLE, PREF_BOOL, PREF_STRING, PREF_MAX} pref_value_type_t;

typedef struct _key_value 
{
   char *key;
   void *value;
   pref_value_type_t value_type;
} key_value_t;
typedef void (*preference_read_cb)(key_value_t *key_value);

Application Initialization

The entire application life-cycle is implemented in the preferences.c file, using a common Tizen application structure:

int
main(int argc, char *argv[])
{
   appdata_s ad = {{0,},};
   int ret = 0;

   ui_app_lifecycle_callback_s event_callback = {0,};
   app_event_handler_h handlers[5] = {NULL,};

   event_callback.create = app_create;
   event_callback.terminate = app_terminate;
   event_callback.pause = app_pause;
   event_callback.resume = app_resume;
   event_callback.app_control = app_control;

   ui_app_add_event_handler(&handlers[APP_EVENT_LOW_BATTERY], APP_EVENT_LOW_BATTERY, ui_app_low_battery, &ad);
   ui_app_add_event_handler(&handlers[APP_EVENT_LOW_MEMORY], APP_EVENT_LOW_MEMORY, ui_app_low_memory, &ad);
   ui_app_add_event_handler(&handlers[APP_EVENT_DEVICE_ORIENTATION_CHANGED], APP_EVENT_DEVICE_ORIENTATION_CHANGED, ui_app_orient_changed, &ad);
   ui_app_add_event_handler(&handlers[APP_EVENT_LANGUAGE_CHANGED], APP_EVENT_LANGUAGE_CHANGED, ui_app_lang_changed, &ad);
   ui_app_add_event_handler(&handlers[APP_EVENT_REGION_FORMAT_CHANGED], APP_EVENT_REGION_FORMAT_CHANGED, ui_app_region_changed, &ad);
   ui_app_remove_event_handler(handlers[APP_EVENT_LOW_MEMORY]);

   ret = ui_app_main(argc, argv, &event_callback, &ad);
   if (ret != APP_ERROR_NONE) 
   {
      dlog_print(DLOG_ERROR, LOG_TAG, "ui_app_main() failed. err = %d", ret);
   }

   return ret;
}

The Preference sample application is implemented using an MVC design pattern. Its initialization is done within the app_create() callback function:

static bool
app_create(void *data)
{
   // Hook to take necessary actions before main event loop starts
   // Initialize UI resources and application data
   // If this function returns true, the main loop of application starts
   // If this function returns false, the application is terminated
   appdata_s *ad = data;

   return controller_initialize(&ad->vd, &ad->md);
}

In the initialization step, the controller_property_items_enum_item_get() function is used to obtains the PROPERTY_ITEMS_ENUM_KEY preference in the form of an Eina_List list. It contains all the preferences defined by the user together with the value types. Once the list of defined preferences types is known, the user interface is created using the view_create_base_gui() function. Finally, the application model is created with the model_create() function and the defined preference type list is disposed of using the controller_key_value_list_remove() function.

The controller_initialize() function takes 2 parameters (for details, see Type Definitions):

  • viewdata_s *vd is a pointer to a static structure whose members are referencing UI components.
  • modeldata_s *md is a pointer to a static structure, referencing the preference type selected through the UI selector.
bool
controller_initialize(viewdata_s *vd, modeldata_s *md)
{
   Eina_List *pref_types = NULL;

   if (!controller_property_items_enum_item_get(&pref_types)) 
   {
      return false;
   }

   if (!view_create_base_gui(vd)) 
   {
      return false;
   }

   model_create(md, pref_types, preference_read_valid_cb);

   controller_key_value_list_remove(pref_types);

   return true;
}

Inside the controller_initialize() function, the list of user-defined preferences and related types (stored in the PROPERTY_ITEMS_ENUM_KEY internal preference) are retrieved with the controller_property_items_enum_item_get() function.

At the entry point, you check whether the PROPERTY_ITEMS_ENUM_KEY preference has already been defined (model_preference_exists_check()). If yes, its value is retrieved with the model_preference_string_get() function. On success, the string composed of key_name:value_type pairs is returned:

key_name_1:value_type;key_name_2:value_type; ... ;key_name_N:value_type;

The string of property types is then decomposed into a list of key_name:value_type pairs using the property_items_enum_key_type_decompose() function (this function listing is not provided as it is a simple string decomposition that uses the tokenized property of the string). The decomposition result is stored in the properties_enum variable of the Eina_List type.

The properties_enum list consists of a number of structures of the key_value_t type (for details, see Type Definitions).

bool
controller_property_items_enum_item_get(Eina_List **properties_enum)
{
   bool key_exists = model_preference_exists_check(PROPERTY_ITEMS_ENUM_KEY);

   char *items_enum_value = NULL;
   if (key_exists) 
   {
      if (!model_preference_string_get(PROPERTY_ITEMS_ENUM_KEY, &items_enum_value)) 
      {
         return false;
      }
   }

   bool ret = property_items_enum_key_type_decompose(items_enum_value, properties_enum);

   if (items_enum_value) 
   {
      free(items_enum_value);
   }

   if (!ret) 
   {
      return false;
   }

   return true;
}

The user interface is created with the view_create_base_gui() function (for more details, see View Implementation). Once the user interface is created successfully, the application model is created using the model_create() function. This function is responsible for enumerating all the user-defined preferences only using the Preference API's preference_foreach_item() function.

The model_create() function takes 3 parameters:

  • modeldata_s *md is a pointer to the static structure, referencing the preference type selected through the UI selector (for details, see Type Definitions).
  • Eina_List *pref_types is a list of the key_value_t type structures (for details, see Type Definitions).
  • preference_read_cb func_cb is a callback function called for each preference read from the underlying database for which the key name matches the key name stored in the pref_types list. At this point, the preference key-value with type matching is performed (basically, this is the startup workflow, as shown in the Application workflow figure).
void
model_create(modeldata_s *md, Eina_List *pref_types, preference_read_cb func_cb)
{
   pref_foreach_data_s pref_foreach_data = 
   {
      .pref_types = pref_types,
      .func_cb = func_cb,
   };

   modeldata = md;

   int ret = preference_foreach_item(preference_item_enum_cb, (void*)&pref_foreach_data);
   if (ret != PREFERENCE_ERROR_NONE) 
   {
      controller_log(DLOG_ERROR, "Function preference_foreach_item() failed with error %d", ret);
   }
}

Note that the mechanism of doubled callback functions is used. It means that the preference_foreach_item() function calls the preference_item_enum_cb() function for each preference found in the underlying database. The preference_item_enum_cb() callback function is called from the application model.

Within the preference_item_enum_cb() function, the additional callback function (func_cb() of the preference_read_cb type) is called whenever a key-value is successfully matched with the preference type. The func_cb() callback function is called in the application controller.

This doubled approach is used to deliver only those preferences whose key values are successfully matched with the related data type to the controller. The following code snippet illustrates the matching procedure in the preference_item_enum_cb() function:

static bool
preference_item_enum_cb(const char *key, void *user_data)
{
   // Extracting data passed to the preference_foreach_item() function
   pref_foreach_data_s *data = (pref_foreach_data_s*)user_data;	
   Eina_List *it = NULL;
   key_value_t *key_val = NULL;
   int val_type = -1;

   // Iteration over key_value_t structures decoded from the PROPERTY_ITEMS_ENUM_KEY
   // to find a matching preference type for the key name passed to the preference_item_enum_cb
   EINA_LIST_FOREACH(data->pref_types, it, key_val) 
   {
      if (!controller_same_string_check((char*)key, key_val->key)) 
      {
         continue;
      }

      val_type = (int)key_val->value_type;
      break;
   }

   // If the matching preference type is found, the additional callback function func_cb
   // is called in the application controller. func_cb gets full information about the preference
   //  defined by the user (key-name, value, value-type).
   if (data->func_cb) 
   {
      key_value_t *kv = (key_value_t*)malloc(sizeof(key_value_t));
      kv->key = strdup(key_val->key);
      kv->value = NULL;
      kv->value_type = key_val->value_type;

      // Obtain the preference value with respect to its data type
      if (controller_preference_get(key_val->key, key_val->value_type, &kv->value)) 
      {
         data->func_cb(kv);
      }
   }

   // Finally, the callback function for the preference value change is bonded to the key,
   // which was successfully matched with related data type
   int ret = preference_set_changed_cb(key, preference_item_changed_cb, NULL);

   return true;
}

If the above callback function is called successfully, as a result, the preference_read_valid_cb callback function is invoked through the data->func_cb(kv). The second callback function invocation is responsible for updating the view and the list of preferences.

void
preference_read_valid_cb(key_value_t *key_value)
{
   view_genlist_item_update(key_value);
}

As a final step within the controller_initialize() function, the list of items obtained using the controller_property_items_enum_item_get() function is freed with the controller_key_value_list_remove() function:

void
controller_key_value_list_remove(Eina_List *list)
{
   if (!list) 
   {
      return;
   }

   key_value_t *key_val = NULL;

   EINA_LIST_FREE(list, key_val) 
   {
      controller_key_value_remove(key_val);
   }
}

void
controller_key_value_remove(key_value_t *key_value)
{
   if (!key_value) 
   {
      return;
   }

   if (key_value->key) 
   {
      free(key_value->key);
   }

   free(key_value);
}

View Implementation

The entire application layout is implemented using EDJE scripts. All the top level swallows are designed for EFL Elementary UI component embedding. The following EDJE swallow - EFL Elementary UI component relations and assigned functionalities are used:

  • pref_buttons_panel_left_button: elm_button used to remove all defined preferences
  • pref_buttons_panel_right_button: elm_button used to update (add or alter) a preference with a provided key name, value, and value type
  • pref_edit_panel_key_panel_entry: elm_entry used as a preference key name input field
  • pref_edit_panel_value_panel_entry: elm_entry used as a preference value input field
  • pref_edit_panel_type_panel_entry: elm_hoversel used as a preference value type selector (integer, double, boolean, or string);
  • main_panel_pref_list_panel: elm_genlist used as a list of all defined preferences, whose list items consist of:
    • Key name, value, and value type
    • X button for deleting a specific preference

The following table defines the code snippets that create the UI layout.

Table: UI layout code snippets and figures
Code snippet Figure
The main layout is defined in the preference.edc file:
collections 
{
   group 
   {
      name: GROUP_MAIN;

      parts 
      {
         // The part occupies the entire window
         part 
         {
            name: PART_MAIN_BACKGROUND; // "background"
            type: RECT;
            description 
            {
               color: 0 0 0 255;
            }
         }

         // The part is positioned in relation to PART_MAIN_BACKGROUND
         // The spacer occupies the entire PART_MAIN_BACKGROUND area with small margin all around
         part 
         {
            name: PART_MAIN_MAIN_PANEL; // "main_panel"
            type: SPACER;
         }

         // The part is positioned in relation to PART_MAIN_MAIN_PANEL
         // The swallow occupies 10% of PART_MAIN_MAIN_PANEL height
         part 
         {
            name: PART_MAIN_PREF_BUTTONS_PANEL; // "main_panel_pref_buttons_panel"
            type: SWALLOW;
         }

         // The part is positioned in relation to PART_MAIN_MAIN_PANEL
         // The swallow occupies 40% of PART_MAIN_MAIN_PANEL height
         part 
         {
            name: PART_MAIN_PREF_EDIT_PANEL; // "main_panel_pref_edit_panel"
            type: SWALLOW;
         }

         // The part is positioned in relation to PART_MAIN_MAIN_PANEL
         // The swallow has flexible height which depends on the height of swallows placed above
         // At this configuration, the swallow occupies 50% of PART_MAIN_MAIN_PANEL height
         part 
         {
            name: PART_MAIN_PREF_LIST_PANEL; // "main_panel_pref_list_panel"
            type: SWALLOW;
         }
      }
   }
}

Main layout

The PART_MAIN_PREF_BUTTONS_PANEL swallow is used as a container for the button layout defined in the pref_buttons_panel.edc file:
collections 
{
   group 
   {
      name: GROUP_PREF_BUTTONS_PANEL;

      parts 
      {
         // The part is positioned in relation to PART_MAIN_PREF_BUTTONS_PANEL from preference.edc file
         // The rect plays a role of a background for buttons panel and occupies the entire area
         // of the PART_MAIN_PREF_BUTTONS_PANEL swallow
         part 
         {
            name: PART_PREF_BUTTONS_PANEL_BACKGROUND; // "pref_buttons_panel_background"
            type: RECT;
            description 
            {
               color: 0 0 0 255;
            }
         }

         // The part is positioned in relation to PART_PREF_BUTTONS_PANEL_BACKGROUND
         // The swallow occupies the entire height of the PART_PREF_BUTTONS_PANEL_BACKGROUND with
         // a small margin at the top and the bottom. The swallow width is 50% of the
         // PART_PREF_BUTTONS_PANEL_BACKGROUND width with a small margin at the left and right
         // sides. It is located in the top half of the PART_PREF_BUTTONS_PANEL_BACKGROUND swallow
         part 
         {
            name: PART_PREF_BUTTONS_PANEL_LEFT_BUTTON; // "pref_buttons_panel_left_button"
            type: SWALLOW;
         }

         // The part is positioned in relation to PART_PREF_BUTTONS_PANEL_BACKGROUND
         // The swallow sizing specification is exactly the same as for
         // the PART_PREF_BUTTONS_PANEL_BACKGROUND swallow
         // It is located in the bottom half of the PART_PREF_BUTTONS_PANEL_BACKGROUND swallow
         part 
         {
            name: PART_PREF_BUTTONS_PANEL_RIGHT_BUTTON; // "pref_buttons_panel_right_button"
            type: SWALLOW;
         }
      }
   }
}

Button layout

The main_panel_pref_edit_panel swallow (defined in the main layout) is used to embed the input panel layout defined in the pref_edit_panel.edc file:
collections 
{
   group 
   {
      name: GROUP_PREF_EDIT_PANEL;

      parts 
      {
         // The part is positioned in relation to main_panel_pref_edit_panel from preference.edc file
         // The rect plays a role of the background for the input panel and occupies the entire area
         // of the PART_MAIN_PREF_EDIT_PANEL
         part 
         {
            name: PART_PREF_EDIT_PANEL_BACKGROUND; // "pref_edit_panel_background"
            type: RECT;
            description 
            {
               color: 0 0 0 255;
            }
         }

         // ----------=============== KEY INPUT PANEL ===============----------

         // The part is positioned in relation to PART_PREF_EDIT_PANEL_BACKGROUND
         // The spacer occupies 33% of the PART_PREF_EDIT_PANEL_BACKGROUND height
         // It is located at the top of the PART_PREF_EDIT_PANEL_BACKGROUND
         // part
         part 
         {
            name: PART_PREF_EDIT_PANEL_KEY_PANEL; // "pref_edit_panel_key_panel"
            type: SPACER;
         }

         // The part is positioned in relation to PART_PREF_EDIT_PANEL_KEY_PANEL
         // The text part occupies the entire height and 30% width of the PART_PREF_EDIT_PANEL_KEY_PANEL
         // This part is responsible for static text label display only
         // (LABEL_KEY_CAPTION == "Key name").
         part 
         {
            name: "pref_edit_panel_key_panel_label";
            type: TEXT;
            description
            {
               text
               {
                  align: 0.5 0.5;
                  text: LABEL_KEY_CAPTION;
                  size: 27;
               }
            }
         }

         // The part is positioned in relation to PART_PREF_EDIT_PANEL_KEY_PANEL
         // The rect plays a role of a background for the elm_entry component. Its width is flexible
         // and occupies all the area remaining from pref_edit_panel_key_panel_label part positioning.
         // The height is set to 70% of the PART_PREF_EDIT_PANEL_KEY_PANEL height and it is
         // vertically centered
         part 
         { 
            // "pref_edit_panel_key_panel_entry_background"
            name: PART_PREF_EDIT_PANEL_KEY_PANEL_ENTRY_BACKGROUND;
            type: RECT;
         }

         // The part is positioned in relation to PART_PREF_EDIT_PANEL_KEY_PANEL
         // The swallow occupies entire height of the PART_PREF_EDIT_PANEL_KEY_PANEL part
         // and 70% of its width. The swallow is located just to the right from the
         // pref_edit_panel_key_panel_label text part
         part 
         {
            name: PART_PREF_EDIT_PANEL_KEY_PANEL_ENTRY; // "pref_edit_panel_key_panel_entry"
            type: SWALLOW;
         }

         // ----------=============== VALUE INPUT PANEL ===============----------

         // The layout of the PART_PREF_EDIT_PANEL_VALUE_PANEL part is exactly the same
         // as the layout of the PART_PREF_EDIT_PANEL_KEY_PANEL part. The only exception
         // is that its vertical location is set to the 33% of PART_PREF_EDIT_PANEL_BACKGROUND height
         // For this reason, the source code is not listed here

         // ----------=============== TYPE INPUT PANEL ===============----------

         // The layout of the PART_PREF_EDIT_PANEL_TYPE_PANEL part is exactly the same
         // as the layout of the PART_PREF_EDIT_PANEL_KEY_PANEL part. There are only 2 exceptions:
         // 1. Its height is set to 34% of the PART_PREF_EDIT_PANEL_BACKGROUND height
         // 2. Its vertical location is set to the 66% of PART_PREF_EDIT_PANEL_BACKGROUND height
         // For this reason, the source code is not listed here
      }
   }
}

Input layouts

Based on the layout defined with EDJE scripts, the application interface is created with the view_create_base_gui() function, which takes 1 parameter (a pointer to the structure containing the view data). In succeeding calls to the *_layout_create() functions, the user interface is created.

bool
view_create_base_gui(viewdata_s *vd)
{
   viewdata = vd;

   if (!main_layout_create(vd)) 
   {
      return false;
   }

   if (!pref_edit_panel_layout_create(vd)) 
   {
      return false;
   }

   if (!pref_buttons_panel_layout_create(vd)) 
   {
      return false;
   }

   if (!pref_list_panel_layout_create(vd)) 
   {
      return false;
   }

   eext_object_event_callback_add(vd->main_layout, EEXT_CALLBACK_BACK, layout_back_cb, vd);

   evas_object_show(vd->win);

   return true;
}

The following table defines the base view creation details.

Table: Base view creation code snippets and figures
Description Code snippet Figure
main_layout_create():

The main view is created by loading the main group from the EDJE layout (preference.edj file). The group is embedded to the elm_layout container, which is inserted into the elm_conformant component.

static bool
main_layout_create(viewdata_s *vd)
{
   vd->win = window_create();

   vd->conform = conformant_create(vd->win);

   vd->main_layout = layout_create(vd->conform, EDJ_FILE_NAME_MAIN, GROUP_MAIN, NULL);

   return true;
}

Main view

pref_edit_panel_layout_create():

The edit subview is created by loading the pref_edit_panel group from the EDJE layout (pref_edit_panel.edc file). It is embedded to the elm_layout container, which is inserted into the main_panel_pref_edit_panel swallow of the main layout.

The preference_type_selected_cb() callback function is assigned to each of the selector items. This function is invoked on the selected event of the elm_hoversel item.

static bool
pref_edit_panel_layout_create(viewdata_s *vd)
{
   vd->pref_edit_panel_layout = layout_create(vd->main_layout,
                                              EDJ_FILE_NAME_PREF_EDIT_PANEL,
                                              GROUP_PREF_EDIT_PANEL,
                                              PART_MAIN_PREF_EDIT_PANEL);

   vd->pref_edit_panel_key_entry = entry_create(vd->pref_edit_panel_layout,
                                                PART_PREF_EDIT_PANEL_KEY_PANEL_ENTRY);

   vd->pref_edit_panel_value_entry = entry_create(vd->pref_edit_panel_layout,
                                                  PART_PREF_EDIT_PANEL_VALUE_PANEL_ENTRY);

   vd->pref_edit_panel_type_selector = hoversel_create(vd->pref_edit_panel_layout,
                                                       PART_PREF_EDIT_PANEL_TYPE_PANEL_ENTRY);

   // Type names are added to the elm_hoversel component. On item selection,
   // the preference_type_selected_cb callback function will be invoked with
   // the pointer to the item of the statically defined array of type names.
   for (int i = 0; i < PREF_MAX; i++) 
   {
      elm_hoversel_item_add(vd->pref_edit_panel_type_selector,
                            preftypes[i].caption,
                            NULL,
                            ELM_ICON_NONE,
                            preference_type_selected_cb,
                            (void*)&preftypes[i]);
   }

   return true;
}

Edit panel view

pref_buttons_panel_layout_create():

The button subview is created by loading the pref_buttons_panel group from the EDJE layout (pref_buttons_panel.edc file). It is embedded to the elm_layout container, which is inserted into the main_panel_pref_buttons_panel swallow of the main layout.

The remove_all_click_cb() callback function is assigned to the left button. This function is invoked on the left button clicked event. The update_click_cb() callback function is assigned to the right button. This function is invoked on the right button clicked event. For the implementation details, see Adding and Removing Properties.

static bool
pref_buttons_panel_layout_create(viewdata_s *vd)
{
   vd->pref_buttons_panel_layout = layout_create(vd->main_layout,
                                                 EDJ_FILE_NAME_PREF_BUTTONS_PANEL,
                                                 GROUP_PREF_BUTTONS_PANEL,
                                                 PART_MAIN_PREF_BUTTONS_PANEL);

   vd->pref_buttons_panel_left_button = button_create(vd->pref_buttons_panel_layout,
                                                      PART_PREF_BUTTONS_PANEL_LEFT_BUTTON,
                                                      REMOVE_ALL_PREFS_CAPTION,
                                                      remove_all_click_cb,
                                                      NULL);

   vd->pref_buttons_panel_right_button = button_create(vd->pref_buttons_panel_layout,
                                                       PART_PREF_BUTTONS_PANEL_RIGHT_BUTTON,
                                                       UPDATE_PREF_CAPTION,
                                                       update_click_cb,
                                                       (void*)vd);

   return true;
}

Button view

pref_list_panel_layout_create():

The list subview does not have any EDJE layout. Simply, the elm_genlist component is embedded into the main_panel_pref_list_panel swallow of the main layout.

Items are added to the elm_genlist component on the application model creation. For this purpose, the view_genlist_item_update() function is used.

static bool
pref_list_panel_layout_create(viewdata_s *vd)
{
   vd->pref_list_panel_list = genlist_create(vd->main_layout,
                                             PART_MAIN_PREF_LIST_PANEL,
                                             (void*)vd);

   return true;
}

List view

To add items to the elm_genlist component, the view_genlist_item_update() function takes 1 pointer parameter to the key_value_t structure type (for details, see Type Definitions). It contains the name of the preference key, and its value together with its type.

Based on the key name, the genlist_item_find() function looks for an item in the elm_genlist utilizing a temporary list that references the elm_genlist items (see viewdata_s.genlist_items in Type Definitions). If the item is found successfully:

  1. The data pointer bound to the item is obtained with the elm_object_item_data_get() function and disposed with the controller_key_value_remove() function.
  2. In place of the disposed data, a new data is bound (elm_object_item_data_set()) to the item.
  3. The entire elm_genlist is finally updated with the elm_genlist_item_updated() function.

If the item is not found, a new preference is created by adding it to the elm_genlist using the view_genlist_item_add() function.

bool
view_genlist_item_update(key_value_t *key_value)
{
   Elm_Object_Item *item = NULL;

   // If an item with given key name already exists in the elm_genlist
   if (genlist_item_find(key_value->key, &item)) 
   {
      // Data pointer assigned to the item is retrieved
      key_value_t *item_key_value = elm_object_item_data_get(item);

      // and removed
      controller_key_value_remove(item_key_value);
      // New data pointer is assigned to the elm_genlist item
      // This data describes the preference (key name, value, and value type)
      elm_object_item_data_set(item, (void*)key_value);
      // Updating the list view
      elm_genlist_item_update(item);

      return true;
   }

   // If an item with given key name does not exist in the elm_genlist,
   // a new item is added together with the data describing related
   // preference (key name, value, and value type)
   return view_genlist_item_add(key_value);
}
static bool
genlist_item_find(const char *key, Elm_Object_Item **item)
{
   key_value_t *kv = NULL;
   Eina_List *l;

   // Itemization of all items and looking for an item with
   // matching preference key name
   EINA_LIST_FOREACH(viewdata->genlist_items, l, *item) 
   {
      // Obtain the pointer to the data bound to the item
      kv = elm_object_item_data_get(*item);

      // Check whether item key name matches the key being searched
      // The function below performs case-insensitive strings comparison
      if (controller_same_string_check(kv->key, (char*)key)) 
      {
         return true;
      }
   }

   return false;
}
bool
view_genlist_item_add(key_value_t *key_value)
{
   // Creating new elm_genlist item
   Elm_Genlist_Item_Class *itc = elm_genlist_item_class_new();

   itc->item_style = "double_label"; // Item style (2 text labels and content swallow)
   itc->func.text_get = genlist_item_label_get; // Function setting the item text
   itc->func.content_get = genlist_item_content_get; // Function setting the item swallow content
   itc->func.state_get = NULL;
   itc->func.del = genlist_item_del; // Function disposing an item

   // Item is appended to the elm_genlist
   Elm_Object_Item *item = elm_genlist_item_append(viewdata->pref_list_panel_list, itc, 
                                                   (void *)key_value, NULL, 
                                                   ELM_GENLIST_ITEM_NONE, NULL, NULL);

   // Created item is stored in temporary list for easier access
   viewdata->genlist_items = eina_list_append(viewdata->genlist_items, item);

   return true;
}

The following code snippet shows the procedure for setting the item text labels and swallow content:

static char*
genlist_item_label_get(void *data, Evas_Object *obj, const char *part)
{
   key_value_t *key_val = (key_value_t*)data;

   // Populating 2 buffers (buff_main and buff_sub) with string content
   // based on key_val:
   //    - buff_main - preference key name and its value is assigned in the form
   //      of "key_name = value" string. The value is obtained from key_val->value
   //      of void* type by typecasting using key_val->value_type for types
   //      distinguishing
   //    - buff_sub - preference value type is assigned in the string format:
   //      "type: INTEGER || DOUBLE || BOOLEAN || STRING" based on key_val->value_type

   if (controller_same_string_check((char*)part, "elm.text")) 
   {
      return strdup(buff_main);
   } 
   else if (controller_same_string_check((char*)part, "elm.text.sub")) 
   {
      return strdup(buff_sub);
   }

   return NULL;
}

static Evas_Object*
genlist_item_content_get(void *data, Evas_Object *obj, const char *part)
{
   // This function creates an elm_image component with predefined visual content
   if (!strcmp(part, "elm.swallow.icon")) 
   {
      Evas_Object *image = elm_image_add(obj);

      char *file_path = image_file_path_get(ICON_DELETE_FILE_NAME);
      if (!elm_image_file_set(image, file_path, NULL)) 
      {
      }

      // The callback function is assigned to the elm_image component and is fired
      // for "clicked" event
      evas_object_smart_callback_add(image, "clicked", genlist_item_image_clicked_cb, data);

      return image;
   }

   return NULL;
}

Each item added to the elm_genlist has a delete button assigned in the form of the elm_image component with a predefined X image. In the code snippet above, the genlist_item_image_clicked_cb() callback function is assigned to each created elm_genlist item. This function is responsible for the chosen item and related preference deletion. Form more details, see the following code snippet and Controller Implementation.

static void
genlist_item_image_clicked_cb(void *data, Evas_Object *obj, void *event_info)
{
   key_value_t *key_val = (key_value_t*)data;

   // Preference removal from the database
   if (controller_preference_remove(key_val->key)) 
   {

      Elm_Object_Item *item = NULL;

      // If an item with given key name exists on the genlist,
      // it is removed
      if (genlist_item_find(key_val->key, &item)) 
      {
         elm_object_item_del(item);
      }
   }
}

Adding and Removing Properties

The procedure for adding and removing properties is triggered from the UI as a result of invoking a callback function for the button "clicked" event:

  • update_click_cb()

    If all the data (key name, value, and value type) is provided correctly and the key name does not exist in the preference database, a new preference is created. Otherwise, the preference with the existing key name is updated. Finally, all the changes are reflected in the UI.

    static void
    update_click_cb(void *data, Evas_Object *obj, void *event_info)
    {
       // Preference key name, value, and value type is obtained from the UI components
    
       // Newly defined preference is added to the list of user-defined preferences (PROPERTY_ITEMS_ENUM_KEY).
       // If the preference already exists in the PROPERTY_ITEMS_ENUM_KEY, only the value and data type
       // are updated
       controller_property_items_enum_item_add(key, type);
    
       // The defined preference is stored in preferences database. The "value"
       // parameter is passed to the controller_preference_set() function
       // as a char pointer (string from the elm_entry component)
       if (controller_preference_set(key, value, type)) 
       {
    
          // If the preference was successfully added, its value is obtained again
          // to convert the value to the proper data type
          if (controller_preference_get(key, type, &key_value)) 
          {
    
             // Updating the elm_genlist component
             view_genlist_item_update(kv);
          }
       } 
       else 
       {
          // Error handling
       }
    
       free(key);
    }
    

    For the implementation details of the controller_property_items_enum_item_add(), controller_preference_set(), and controller_preference_get() functions, see Controller Implementation. For the implementation details of the view_genlist_item_update() function, see View Implementation.

  • remove_all_click_cb()

    All the preferences are removed with 1 operation.

    static void
    remove_all_click_cb(void *data, Evas_Object *obj, void *event_info)
    {
       if (!model_preferences_remove()) 
       {
          return;
       }
    
       elm_genlist_clear(viewdata->pref_list_panel_list);
    }
    

Model Implementation

The responsibility of the application model module is to operate directly on the Preference API and related data. The additional benefit of this module is the simplification of the API function calling: the error checking and message logging is performed here.

There are several functions with a general signature in the form of a setter and a getter:

  • bool model_preference_#type#_set(const char *key, #type# value)
  • bool model_preference_#type#_get(const char *key, #type# *value)

Where #type# is 1 of the following data types: int, double, bool, or char*. The generalized implementation of these functions is shown below.

bool
model_preference_#type#_set(const char *key, #type# value)
{
   int ret = preference_set_#type#(key, value);
   if (ret != PREFERENCE_ERROR_NONE) 
   {
      controller_log(DLOG_ERROR, "Function preference_set_#type#() failed with error %d", ret);

      return false;
   }

   return true;
}

bool
model_preference_#type#_get(const char *key, #type# *value)
{
   int ret = preference_get_#type#(key, value);
   if (ret != PREFERENCE_ERROR_NONE) 
   {
      controller_log(DLOG_ERROR, "Function preference_get_#type#() failed with error %d", ret);

      return false;
   }

   return true;
}

The preferences deletion can be performed as a single or batch operation using the following functions:

  • bool model_preference_remove(const char *key)
  • bool model_preferences_remove(void)

The implementation of the 2 functions is very similar and differs only by the API function used. In the first case, the preference_remove() function is used, while the second one is based on the preference_remove_all() function call. The implementation structure is the same as listed above except for the API function used.

The last feature provided by the application model is the validation of the preference existence. This can be performed with the model_preference_exists_check() function. The implementation structure is the same as listed above except for the API function used.

Controller Implementation

The Controller module is responsible for sharing the functionality of the Model with the View module. Some additional logic is executed within the Controller to perform model-related operations and deliver the results to the application view.

During the application initialization and preference adding and updating procedure, the controller_property_items_enum_item_add() and controller_property_items_enum_item_get() functions are used. They are used to control the process of the key_name:value_type tuple storage and retrieval.

bool
controller_property_items_enum_item_add(const char *key_name, pref_value_type_t value_type)
{
   bool key_exists = model_preference_exists_check(PROPERTY_ITEMS_ENUM_KEY);
   char *items_enum_value = NULL;

   if (key_exists) 
   {
      if (!model_preference_string_get(PROPERTY_ITEMS_ENUM_KEY, &items_enum_value)) 
      {
         return false;
      }
   }

   char *items_enum_new = NULL;
   bool ret = property_items_enum_key_type_compose(items_enum_value, key_name, value_type, &items_enum_new);

   if (!model_preference_string_set(PROPERTY_ITEMS_ENUM_KEY, items_enum_new)) 
   {
      free(items_enum_new);

      return false;
   }

   free(items_enum_new);

   return true;
}

First of all, the existence of the PROPERTY_ITEMS_ENUM_KEY property is verified and its value is obtained, if it exists. The property_items_enum_key_type_compose() function call performs the merge operation with the key_name:value_type tuple (function parameters) to the obtained value of PROPERTY_ITEMS_ENUM_KEY. The implementation of the property_items_enum_key_type_compose() function is simple, so it is not listed here. The final string is stored using the model_preference_string_set() function.

There are also 2 helper functions used in the Controller module:

  • controller_preference_set() sets the preference in the database with respect to its datatype:
    bool
    controller_preference_set(const char *key, const char *value, pref_value_type_t type)
    {
       int int_value = 0;
    
       switch (type) 
       {
          case PREF_INTEGER:
             // Conversion from string value to the data type specified
             // by the "type" and setting the preference
             if (string_to_int(value, &int_value)) 
             {
                ret = model_preference_int_set(key, int_value);
             }
                break;
          case PREF_DOUBLE:
             // The same approach as for PREF_INTEGER is used
          case PREF_BOOL:
             // The same approach as for PREF_INTEGER is used
          case PREF_STRING:
             // The same approach as for PREF_INTEGER is used
          default:
             // Error handling
       }
    
       // Error handling
    
       return true;
    }
    
  • controller_preference_get() gets the preference with a given key from the database:
    bool
    controller_preference_get(const char *key, pref_value_type_t type, void **value)
    {
       int int_value = 0, size_val = 0;
    
       void *ptr_val = NULL;
    
       switch (type) 
       {
          case PREF_INTEGER:
             // Obtain the preference value for the given key name. Returned value
             // is typecasted to the void pointer for unification. Get the
             // size of the variable required for the storage in a memory
             ret = model_preference_int_get(key, &int_value);
             ptr_val = (void*)&int_value;
             size_val = sizeof(int);
             break;
          case PREF_DOUBLE:
             // The same approach as for PREF_INTEGER is used
          case PREF_BOOL:
             // The same approach as for PREF_INTEGER is used
          case PREF_STRING:
             // The same approach as for PREF_INTEGER is used
          default:
             // Error handling
       }
    
       // Error handling
    
       if (size_val > 0) 
       {
          // Obtained value of the preference is returned as a void pointer
          // stored in designated memory area for further usage
          *value = (void*)malloc(size_val);
          memcpy(*value, ptr_val, size_val);
       }
    
       return true;
    }