This documentation is written against version 16.12 of Apertis. This guide describes creating an agent using the Canterbury legacy application framework. This framework is deprecated and therefore not recommended for new development.

The application generated by the wizard is a set of resources installed as a single unit. It is referred to as a bundle. The app bundle can include open source / proprietary libraries, agents and GUI programs. The UI programs may have one or more entries in the launcher. Applications get launched by tapping the menu entry.

The structure of the example applications follow the MVC (Model-View-Controller) pattern, which makes them easier to maintain.

The following diagram gives an overview of MVC and the responsibilities of each component in the context of the UI framework:

The main advantage of using MVC for the UI framework is that it enables loose coupling and therefore enhances reusability of each its components.

Basic Work Flow in MVC

Assuming a view with 2 widgets (roller and button), the following sequence is executed on any user interaction:

  • User actions like touch, press, release, slide etc are initiated on the widgets.

  • These widgets will pass on these user actions to the Controller’s event engine. The Controller updates the model which holds all data pertaining to a widget.

  • When any data in the widget’s Model is changed, a notification will be issued by the Model to update the View.

Additionally,

  • In case a new view needs to be rendered, the controller will trigger this action and the view shall be updated.

  • The view can also directly interact with the model, by requesting data for example.

Below is the structure of the wizard-generated HelloWorld program:

The main folders are:

  • src – sources. Explained in more detail below

  • resources – This folder has 2 subfolders: data and views.

    • data – this holds the images needed for the views and widgets

    • views – holds the view definition as a json file, as well as the widget properties in another json file.

  • config, m4, SDKConfig, scripts – are used either by build or eclipse tools and need not be disturbed.

Picture below shows the structure of the application’s src folder for the HelloWorld application.

The file HelloWorld_app is the application’s main GObject. It has the following functions:

  • This object registers to canterbury service for new app states.

  • It creates and initializes the model, view and controller classes. In the process of initialization, the view and widgets are created and registration to default events like the views' drawer events is done.

  • All the application logic needs to be contained in the model, view and controller files. The application GObject needs no changes as it is just the initiator and can stay as generated.

Model

The model GObjects provide the Model part of the MVC architecture in the application. It is a placeholder for all data required by the applications – both structured and lists. This datapool is initialised at application startup.

It also uses PDI (Persistent Data Interface) to store persistent elements and restore the last-mode-data during the Application start-up.

In the Helloworld application, HelloWorld_model.c is the GObject where the app can store the collection of data arranged in a structured way. It may be containing the backend data, parsed data etc, whichever an application needs for future reference. When the HelloWorld app is created, the model is empty. That is so that the app developer can decide what to store in the application model.

An application can use any format (simple structure, List of objects, file etc) to store the data it is interested in. However we make this recommendation:

Apertis provides the Clutter library for UI. The Clutter model integrates easily with widgets and notification mechanisms. It also provides a list model. Hence it is recommended to use the Clutter model for Model implementations. It provides interfaces to access (read/write) datapool (DataModel) elements and to register for notifications.

ClutterModel is a data structure in which data is arranged in rows and columns. When data gets added/removed/changed, signals will be emitted from the ClutterModel. Interested modules can connect to it to perform any sort of operations. In MVC, whenever the data changes the View may have to be updated, hence widgets will explicitly connect to the row-added signal and widgets can refresh themselves on model changes.

Below is a code snippet showing an application’s model storing the metadata of songs that it has indexed. This data is then passed on to widgets to display on the screen:

The Model GObject can be an implementation of GListModel — An interface describing a dynamic list of objects. This will localise the clutter dependency to view in the app and can be replaced with any other UI library if desired. The sample apps will be updated to GListModel in future.

View

The View in the MVC design pattern is the output representation of the data, which is presented to the users. Every application has one or many views. Each view has a set of widgets through which the user interacts with the application.

In the Apertis framework, the views and widgets are created and initialised by JSON files. The view definition describes widgets that belong in a given view. Each view is defined in a separate JSON file. All widgets will have a default configuration file associated with them, describing their properties. Application developers can choose the required properties for the widget in a particular view and can change the default values.

Global widgets

Widgets which appear in more than one screen and widgets which are in the app scope and not in the view scope are called global widgets. E.g : Views drawer (needed to switch between views).

They are identified by the same, unique name across views. Only one instance of such global widgets will be created in the app. Due to it being a singleton, the state is maintained across views.

The structure of the views folder in the sample project is shown below:

The view folder will have GObjects for all the views and a window GObject.

The sample project has 3 views – ThumbView, ListView and DetailView and a window class.

The window GObject is the main view object and has these functions:

  • Controlling and initializing the views

  • Interacting with the controller

  • Handling signals from global widgets

Widgets

Widgets provide a wide range of functionalities to cater to the needs of different views belonging to different applications. For example, a button widget can support only the press and release events in one view, but in another view it may also support long press events. These functionalities are portrayed as properties of the widget. The properties of a widget are present in the JSON file.

JSON files

The view and widget JSON files are stored in the resources folder. On expanding the resources folder you can see a folder for each view and their JSON files directly under the resources folder.

The view folders (ListView, ThumbView, etc…) will hold the JSON files of widgets used in the respective views.

The generic form of the json file for a view is as follows:

{

"viewId":"ViewName",

"widgets":{

"WidgetName": ["WidgetGType", "file_name", pos_x, pos_y, layer]

}
  • ViewName is used to identify the view and associate widgets to the mentioned view.

  • file_name corresponds to the property json file which the widget demands. It should have a .json extension which will be appended by default.

  • layer can have a positive value starting from 0 which indicates the layer to which the widget has to be added with 0 being the bottom most layer.

  • WidgetGType specifies the GType of the widget. WidgetName is the name given to uniquely identify the widget and applications can use the same name for further reference. It is the application developer’s responsibility to give a unique WidgetName to the widget without overlapping with any other widget, in which case it would be treated as a global widget.

  • A sample json file for a view, named DetailedView.json is shown below:

Now, if we explore the JSON files inside a particular view, we can find one such file per widget in the folder.

Here is an example for a ViewsDrawer widget:

This file indicates that the properties X and Y of the widget can be modified by the app.

Creation of views

The Apertis SDK provides support to automatically create views and widgets. The approach followed is similar to what Clutter does with Clutter script. The SDK library which does this is view manager.

View manager is the special parser library, which parses view and widget property json files to creates view and initializes widgets with models. It also provides the necessary framework for the UI theme, skinning capabilities and transitions during view switches, and reduces repetitive code and complexity.

The View manager needs to be initialized by the application, this is done in the Window GObject. It registers for the global preferences to get the current theme, skin and language information. Based on the app name, it locates the view definition files from the resource folder location provided by the application. Then it parses the view’s json file and their respective widgets' json files and starts creating widgets for each view. It feeds the current theme and language information to the widgets. The widgets use the theme information to load appropriate CSS files.

In case of multiple views, views' json files have to be created for each such view. All the widgets' json files should be grouped under a directory having the same name as that of the view. If a widget needs to be present in one or more views (global widgets), they can be added in multiple json files sharing the same name. However only one instance of that widget will be created.

View Manager initialization is done in the window GObject:

ThornburyViewManagerAppData pViewData;

pViewData = g_new0(ThornburyViewManagerAppData,1);

/* Application Object which is added to the stage */

pViewData->app = CLUTTER_ACTOR(pHelloWorldWin);

/* Specify the default view */

pViewData->default_view = "ListView";

/* Resource directory Path */

pViewData->viewspath = "/Applications/HelloWorld/share/";

/* Application Run-time name */

pViewData->app_name = g_strdup(pAppName);

pViewData->stage = CLUTTER_STAGE(pHelloWorldWindow->pglobStage);

pViewData->pAppBackFunc = NULL;

pViewData->pScreenShotPath = NULL;

/* create the view manager instance. */

pViewManager = thornbury_view_manager_new(pViewData);

Model creation

Widgets can be assigned data that needs to be displayed using ThornburyModel. This model has been derived from the Clutter model library and supports all the features provided by the Clutter Model. In addition to it, specific signals and functionalities are added to satisfy Apertis-specific requirements.

ThornburyModel (like ClutterModel) is a generic list model. Data is fed in rows and columns to the model. The model is then assigned to the widget. The widget is aware of the model and paints accordingly. Models' implementations should be adapted to their views' requirements . See the API reference for more on that.

The application model can be implemented using any format like a file or a structure. However models assigned to widgets have to inherit from Thornbury model.

Below is a code snippet showing the creation of a model for a views drawer widget. Views drawer is a widget that shows the views available in the app as a button so that when a view button is clicked the application can transition to that view. It needs view name, view icon, tooltip for that view and also a flag to specify if it should be enabled. Each view is added as a row to this model, then it gets assigned to the widget.

The Model then provides necessary signals to the widget to handle the change in data. Some basic signals include row-added, row-removed, row-changed and sort-changed, etc… Widgets register to these signals to update the view correspondingly.

GHashTable pWindowModelHash;

ThornburyModel *model = NULL;

model = (ThornburyModel *) thornbury_list_model_new (DRAWER_COLUMN_LAST,
						     G_TYPE_STRING, NULL,
                                                     G_TYPE_STRING, NULL,
                                                     G_TYPE_STRING, NULL,
                                                     G_TYPE_BOOLEAN, NULL,
                                                     -1);

g_hash_table_insert(pWindowModelHash, GLOBAL_VIEWS_DRAWER ,model);

thornbury_set_widgets_models(pViewManagerPtr, pWindowModelHash);

View manager links the model to the mentioned widgets by calling thornbury_set_widgets_models.

Associating a model to a widget

View Manager creates the GObject of respective widgetTypes given in the views' json files.

Data has to be provided by the user through the Model, which contains data in a format recognized by the widget.

Signal creation

Widgets will provide signals to indicate the occurrence of any event in the view. Views can register to those events and then decide if they can be handled in the view itself, or if they should be forwarded to the controller.

Setting a property on a widget

We can update or modify the properties of the widget at runtime. For example, if we want to close the views drawers before switching to other views, then we set the close property of the views drawer widget to TRUE in a view switch callback we registered.

View Switch

Views can be switched when the user clicks on something on the screen / faceplate or due to system’s reaction.

In general the view switching state machine is part of the controller. However the views have to support controller in view switching. By default, if the events from a widget can not be handled within a particular view, they should be handed over to the controller. In order to do this , the view class has to create a signal and emit it. The controller catches this signal and decides to make the switch.

The switching of views happens in controller, refer to Performing View Switch.

Adding a new widget

To add a new widget to an existing view the below steps needs to be followed:

  • Make an entry in the view JSON file. Open it and add the widget in this format:

    "WidgetNameInTheView": ["WidgetGObjectName", "WidgetPropertyJSONFileName", X_pos, Y_pos, Layernum]
    

    Here’s an example code snippet:

  • The widgets' JSON files can be seen at this path: /usr/share/mildenhall/

  • The widget name should be unique across views

  • Layer number can be comprised between 0 and 999.

  • Copy the widget’s json file to the correct views folder. Restart the application.

  • Now, the widget will be added and initialized by view manager. The view manager uses the view definition file to parse the view information and starts the widget creation process.

  • The application can set the model and connect to signals of the widget using view manager.

Widgets will not paint themselves till a model is assigned to them. Model creation has to be done in the view’s GObject.

Controller

Controller is the decision maker and the glue between the model and the view. It updates the view when the model changes. It also listens to the view’s signals and updates the model when the user manipulates the view.

It has three major responsibilities:

  • Performing view switching

  • Registering to the SDK services / app agents using dbus interfaces (if needed)

The picture below shows the controller Gobject structure in the sample project (HelloWorld)

The controller class holds the reference to all the views' GObjects and connects to signals exposed by views' GObjects. For example, if the user pressed ListView from views-drawer in the UI, the button-press event will be relayed to the controller by the view’s GObject, which switches the current view to ListView.

Performing view switch

Switching between views can happen with or without animation. View manager provides APIs for both cases.

A trigger to switch the view should come from the UI using some widget or from the system itself.

This code snippet shows how to switch views using the view drawer widget.

Connecting with the backend

Controller glues the app with the backend service. It acts like a client to the SDK service or the application service by connecting to the dbus. Once it is registered, it can use the interfaces of the service to interact with it.

An example is the controller GObject of media player registering to the SDK audio service. When user presses pause in the app, the controller can emit a signal or call a dbus method in audio service interface to pause the streaming.

This code snippet shows the app registering to the application service (also called agent).

After registering to backend services, controller can use the fi’s or connect to signals exposed by the service.

For example, controller can call pause / play fi exposed by service to pause / play the media files.

Updating model with information from backend

Controller updates the application model with the data (when there is a change in data or view). Here the controller updates the info widget with the respective artist / album meta detail which is played currently: