The EFL Cookbook

Various

Edited by

Ben 'technikolor' Rockwood

Revision History
Revision v0.1Feb 20th, 2004br
Initial document

Abstract

Stuff.


Table of Contents

1. Introduction
2. Imlib2
Recipe: Image Watermarking
Recipe: Image Scaling
Recipe: Free rotation
Recipe: 90 degree Image rotation
Recipe: Image Flipping
3. EVAS
Recipe: Using Ecore_Evas to simplify X11 canvas initialization
Recipe: Key Binds, using EVAS Key Events
Recipe: Evas Smart Object Introduction
4. Ecore
Recipe: Ecore Config Introduction
Recipe: Ecore Config Listeners
Recipe: Ecore Ipc Introduction
Recipe: Ecore Timers
5. EDB & EET
Recipe: Creating EDB files from the shell
Recipe: EDB introduction
Recipe: EDB key retrieval
6. Esmart
Recipe: Esmart Trans Introduction
Recipe: Esmart Container Introduction
7. Epeg & Epsilon
Recipe: Simple Thumbnailing with Epeg
Recipe: Simple Thumbnailing with Epsilon
8. Etox
Recipe: Etox Overview
9. Edje
Recipe: A Template for building Edje applications
10. Edje EDC & Embryo
Recipe: Edje/Embryo toggle
11. EWL
Recipe: EWL Introduction
12. Evoak
Recipe: Evoak hello client
13. Emotion
Recipe: Quick DVD player with Emotion
Recipe: Expanded Media player with Emotion

List of Examples

2.1. Imlib2 WaterMark Program
2.2. Image Scaling
2.3. Free rotation
2.4. 90 degree Image rotation
2.5. Image Flipping
3.1. Ecore_Evas Template
3.2. Key grabbing using EVAS Events
3.3. EVAS Keybind Compile
3.4. foo.h
3.5. foo.c
3.6. main.c
3.7. Compilation
4.1. Simple Ecore_Config program
4.2. Compilation command
4.3. Simple config.db script (build_cfg_db.sh)
4.4. Ecore_Config listener
4.5. Compilation
4.6. Ecore_Ipc client: preamble
4.7. Ecore_Ipc client: main setup
4.8. Ecore_Ipc client: main creating client
4.9. Ecore_Ipc client: main end
4.10. Ecore_Ipc client: sig_exit_cb
4.11. Ecore_Ipc client: the callbacks
4.12. Ecore_Ipc server: preamble
4.13. Ecore_Ipc server: main setup
4.14. Ecore_Ipc server: main creating server
4.15. Ecore_Ipc client: main end
4.16. Ecore_Ipc server: sig_exit callback
4.17. Ecore_Ipc server: the callbacks
4.18. Ecore_Ipc: compilation
4.19. Ecore Timers
4.20. Compilation
5.1. EDB file shell script
5.2. EDB introduction
5.3. Compiling
5.4. EDB key retrieval
5.5. Compiling
6.1. Includes and declarations
6.2. main
6.3. exit and del callbacks
6.4. _freshen_trans
6.5. resize_cb
6.6. move_cb
6.7. Setup ecore/ecore_evas
6.8. Creating Esmart_Trans object
6.9. Simple makefile
6.10. Includes and declarations
6.11. main
6.12. Initialization
6.13. Shutdown
6.14. Window callbacks
6.15. make_gui
6.16. Edje Callbacks
6.17. container_build
6.18. Adding Elements to the Container
6.19. _set_text
6.20. _left_click_cb
6.21. _right_click_cb
6.22. _item_selected
6.23. The Edc
6.24. The Container Part
6.25. The Element Group
6.26. Makefile
7.1. Epeg Simple Thumbnail
7.2.
7.3. Epsilon Simple Thumbnail
7.4.
8.1. Etox Overview Example
8.2.
9.1. Edje Template
10.1. Creating the variable
10.2. Initializing variables
10.3. The toggler button
10.4. Hooking into the mouse events
10.5. Build script
10.6. Edje toggle without Embryo
11.1. Includes and declarations
11.2. main
11.3. mk_gui: creating the window
11.4. The main container
11.5. Create the menu bar
11.6. Create the scrollpane
11.7. Create the text area
11.8. Add menu contents
11.9. Attach callbacks
11.10. Destroy callback
11.11. File menu open callback
11.12. File dialog destroy callback
11.13. File dialog open button callback
11.14. File dialog home button callback
11.15. Read text file
11.16. Key press callback
11.17. Complication
12.1. Includes and pre-declarations
12.2. main
12.3. Canvas info callback
12.4. Disconnect callback
12.5. setup routine
12.6. Compilation
13.1. Compiling
13.2. DVD player in 55 lines of code
13.3. Emotion Media Player

Chapter 1. Introduction

Welcome to the enlightened state of programming. This cookbook is a collection of tips, tricks, tutorials and introductions arranged into recipe style in order to help you becomes quickly proficience and effective with the Enlightenment Foundation Libraries. The Enlightement Foundations Libraries, refered to simples as the EFL, is a collection of libraries origionally written support the Enlightenment DR17 Window Manager. However, as these libraries grew and were tested and deployed more and more general functionality was added bringing us to enjoy a rich and powerful set of libraries that can solve all sorts of problems and also act as a venerable alternative to the currently popular GTK and QT.

The EFL is a comprehensive group of C libraries that can provide for nearly any type of graphical need on nearly any platform. The following is a consice breakdown of EFL componant libraries.

EFL Componant List

Imlib2

Complete image manipulation library, including rendering to X11 drawables.

EVAS

Canvas library with multiple backend engines including hardware OpenGL acceloration.

Ecore

Modular library collection with event handling and timers, plus sockets, IPC, FB and X11 setup, teardown, and event handling, job control, configuration handling and more.

EDB

A database library capabile of storing strings, values, and data for simple and centralized configuration handling.

EET

A flexable container format for storing binary images and data.

Edje

An image abstraction library and toolset based on EVAS, keeping all aspects of the user interface completely seperate from application code through the use of signal passing.

Embryo

A scripting language typically used with Edje for advanced control.

Etox

A text formating and manipulation library, complete with text stylization capabilities.

Esmart

A library consisting of various EVAS smart objects for easy reuse, including the popular transpancy hack.

Epeg

A lightning fast thunbnailing library for JPEGs independant of other EFL componants.

Epsilon

A flexable and fast thumbnailing library for PNG, XCF, GIF and JPEG.

Evoak

An EVAS canvas server library and toolset.

EWL

A complete widget library.

Emotion

An Evas object library for video and DVD playback using libxine.

Chapter 2. Imlib2

Imlib2 is the successor to Imlib. It is not just a newer version - it is a completely new library. Imlib2 can be installed alongside Imlib 1.x without any problems since they are effectively different libraries - but they Have very similar functionality.

Imlib2 can do the following:

  • Load image files from disk in one of many formats

  • Save images to disk in one of many formats

  • Render image data onto other images

  • Render images to an X-Windows drawable

  • Produce pixmaps and pixmap masks of Images

  • Apply filters to images

  • Rotate images

  • Accept RGBA Data for images

  • Scale images

  • Alpha blend Images on other images or drawables

  • Apply color correction and modification tables and factors to images

  • Render images onto images with color correction and modification tables

  • Render truetype anti-aliased text

  • Render truetype anti-aliased text at any angle

  • Render anti-aliased lines

  • Render rectangles

  • Render linear multi-colored gradients

  • Cache data intelligently for maximum performance

  • Allocate colors automatically

  • Allow full control over caching and color allocation

  • Provide highly optimized MMX assembly for core routines

  • Provide plug-in filter interface

  • Provide on-the-fly runtime plug-in image loading and saving interface

  • Fastest image compositing, rendering and manipulation library for X

If what you want isn't in the list above somewhere then likely Imlib2 does not do it. If it does, it likely does it faster than any other library you can find (this includes gdk-pixbuf, gdkrgb, etc.) primarily because of highly optimized code and a smart subsystem that does the dirty work for you and picks up the pieces for you so you can be lazy and let Imlib2 do all the optimizations for you.

Imlib2 provides a powerful engine for image manipulation and rendering. Using loaders it can handle a variety of image formats including BMP, GIF (via unGIF), JPEG, PNG, PNM, TGA, TIFF, XPM and more.

Recipe: Image Watermarking

Ben technikolor Rockwood

With so many individuals putting so many images online its easy to forget where they came from and hard to ensure that copyrighted material isn't inadvertently misused. Simply adding a watermark image, such as your sites logo, to each of your images can solve both these problems. But adding watermarks manual is a long and repetitive task. Imlib2 can easily be used to solve this problem. What we need to do is take an input image, and then specify a watermark image (your logo), position the watermark on the input image and then save it out to a new image which we'll use on the site. The app would look something like this:

Example 2.1. Imlib2 WaterMark Program

#define X_DISPLAY_MISSING
#include <Imlib2.h>
#include <stdio.h>

int main(int argc, char **argv){

    Imlib_Image image_input, image_watermark, image_output;
    int     w_input, h_input;
    int     w_watermark, h_watermark;
    char    watermark[] = "watermark.png";

    if(argc > 1)  {
        printf("Input image is: %s\n", argv[1]);
        printf("Watermark is: %s\n", watermark);
    }
    else {
        printf("Usage: %s input_image output_imagename\n", argv[0]);
        exit(1);
    }

    image_input = imlib_load_image(argv[1]);
    if(image_input) {
        imlib_context_set_image(image_input);
        w_input = imlib_image_get_width();
        h_input = imlib_image_get_height();
        printf("Input size is: %d by %d\n", w_input, h_input);
        image_output = imlib_clone_image();
    }

    image_watermark = imlib_load_image(watermark);
    if(image_watermark) {
        imlib_context_set_image(image_watermark);
        w_watermark = imlib_image_get_width();
        h_watermark = imlib_image_get_height();
        printf("WaterMark size is: %d by %d\n", 
		w_watermark, h_watermark);
    }

    if(image_output) {
        int dest_x, dest_y;

        dest_x = w_input - w_watermark;
        dest_y = h_input - h_watermark;
        imlib_context_set_image(image_output);

        imlib_blend_image_onto_image(image_watermark, 0, 
		0, 0, w_watermark, h_watermark, 
		dest_x, dest_y, w_watermark, h_watermark);
        imlib_save_image(argv[2]);
        printf("Wrote watermarked image to filename: %s\n", argv[2]);
    }


        return(0);
}

Looking at the example, we first do some really basic argument checking, accepting an input image as the first argument and an output image name for our watermarked copy. Using imlib_load_image() we load the input image and then grab its dimensions using the get functions. With the imlib_clone_image() function we can create a copy of the input image, which will be the base of our watermarked output. Next we load the watermark image, and notice that we then use imlib_context_set_image() to change the context from the input image (image_input) to the watermark image (image_watermark). Now we grab the images dimensions as well. In the final block we do two simple calculations to determine the positioning of the watermark on the output image, in this case I want the watermark on the bottom right-hand corner. The magic function that really does the work in this program is imlib_blend_image_onto_image(). Notice that we change context to the output image before proceeding. The blend function will, as the name suggests, blend two images together which we refer to as the source and destination image. The blend function blends a source image onto the current image context which we designate as the destination. The arguments supplied to imlib_blend_image_onto_image() can look tricky, we need to tell it which source to use (the watermark), whether to merge the alpha channel (0 for no), the dimensions of the source image (x, y, w, h) and the dimensions of the destination image (x, y, w, h). You'll notice that in the example we set the x and y positions of the source (watermark) image to 0 and then use the full width. The destination (input image) is set to the bottom right hand corner minus the dimensions of the watermark, and then we specify the width and height of the watermark. Finally, we use the imlib_save_image() function to save the output image.

While this example should be significantly improved for real use, it outlines the basics of Imlib2 blending to solves a very common problem efficiently.

Recipe: Image Scaling

dan 'dj2' sinclair

As more people gain the ability to put images on the Internet it is often desired to scale those images to a smaller size to reduce bandwidth. This can easily be solved using a simple Imlib2 program.

This recipe takes the input image name, the new width, new height and the output image name and scales the input image by the given values, saving it back to the output image.

Example 2.2. Image Scaling

#define X_DISPLAY_MISSING

#include <Imlib2.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char ** argv) {
    Imlib_Image in_img, out_img;
    int w, h;

    if (argc != 5) {
        fprintf(stderr, "Usage: %s [in_img] [w] [h] [out_img]\n", argv[0]);
        return 1;
    }

    in_img = imlib_load_image(argv[1]);
    if (!in_img) {
        fprintf(stderr, "Unable to load %s\n", argv[1]);
        return 1;
    }
    imlib_context_set_image(in_img);

    w = atoi(argv[2]);
    h = atoi(argv[3]);
    out_img = imlib_create_cropped_scaled_image(0, 0, imlib_image_get_width(),
                                            imlib_image_get_height(), w, h );
    if (!out_img) {
        fprintf(stderr, "Failed to create scaled image\n");
        return 1;
    }

    imlib_context_set_image(out_img);
    imlib_save_image(argv[4]);
    return 0;
}

There is minimal argument checking done by this example, just make sure we have the correct number of arguments.

The source image is loaded with a call to imlib_load_image() which will load the image data into memory. If the call fails, NULL will be returned. Once we have the image data we need to set the image to be the current context. This lets Imlib2 know which image the operations will be preformed upon. This is done by calling imlib_context_set_image(). Once the image is set as the current context we can proceed with the scale. This is done by calling imlib_create_cropped_scaled_image() which takes as arguments, the starting x position, starting y position, the source width, source height, and the scaled width and scaled height. The reason we pass in the source information is that this function can also crop your image if desired. To crop, just modify the x, y, source width and source height as desired. This will result in a new image being produced out_img. If the scale fails, NULL will be returned. We then set the out_img to be the current context image and issue the save command, imlib_save_image().

Although this program is simple, it shows the simplicity of image scaling using the Imlib2 API.

Recipe: Free rotation

dan 'dj2' sinclair

It is sometimes desirable to rotate an image to some specific angle. Imlib2 makes this process easy. This example attempts to shows how its done. If you wish to rotate the image on angles of 90 degrees, see the 90 degree rotation recipe as this recipe will leave a black border around the image.

Example 2.3. Free rotation

#define X_DISPLAY_MISSING

#include <Imlib2.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

int main(int argc, char ** argv) {
    Imlib_Image in_img, out_img;
    float angle = 0.0;
    
    if (argc != 4) {
        fprintf(stderr, "Usage: %s [in_img] [angle] [out_img]\n", argv[0]);
        return 1;
    }
    
    in_img = imlib_load_image(argv[1]);
    if (!in_img) {
        fprintf(stderr, "Unable to load %s\n", argv[1]);
        return 1;
    }
    imlib_context_set_image(in_img);
    
    angle = (atof(argv[2]) * (M_PI / 180.0));
    out_img = imlib_create_rotated_image(angle);
    if (!out_img) {
        fprintf(stderr, "Failed to create rotated image\n");
        return 1;
    }
 
    imlib_context_set_image(out_img);
    imlib_save_image(argv[3]);
    return 0;
}

After some simple argument checking we get into the Imlib2 work. We begin by loading the specified image into memory with imlib_load_image() giving the image name as a parameter. We then take that image and make it the current context with imblib_context_set_image. Contexts are used by Imlib2 so it knows what image to work on. Whenever you wish to make imlib calls on an image it must be set as the current context. We then convert the given angle from Degrees to Radians as Imlib2s rotation function works in Radians. The rotation is then done with imlib_create_rotated_image(). The rotation function will return the new image. In order to save the new image we need to set it as the current context, again with imlib_context_set_image(). Then a simple call to imlib_save_image() giving the name of the output file saves the new, rotated image.

The rotation function in Imlib2 will place a black border around the image to fill in any blank space. This border is calculated so that the rotated image can fit in the output. This will case borders around the output image even if you rotate by 180 degrees.

Recipe: 90 degree Image rotation

dan 'dj2' sinclair

With a digital camera it is sometimes desirable to rotate you image by: 90, 180 or 270 degrees. This recipe will show how to do this easily with Imlib2. This recipe, also, will not put the black borders around the image as is seen in the free rotate example.

Example 2.4. 90 degree Image rotation

#define X_DISPLAY_MISSING

#include <Imlib2.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char ** argv) {
    Imlib_Image in_img;
    int dir = 0;

    if (argc != 3) {
        fprintf(stderr, "Usage: %s [in_img] [out_img]\n", argv[0]);
        return 1;
    }
    
    in_img = imlib_load_image(argv[1]);
    if (!in_img) {
        fprintf(stderr, "Unable to load %s\n", argv[1]);
        return 1;
    }
    imlib_context_set_image(in_img);
    imlib_image_orientate(1);
    imlib_save_image(argv[2]);
    return 0;
}   

After some minimal error checking we load the image to be rotated with a call to imlib_load_image(). This function accepts a filename and returns the Imlib_Image object, or NULL on load error. Once the image is loaded we set it as the current context image, the image Imlib2 will do its operations upon, with imlib_context_set_image(). The rotation is done through the call to: imlib_image_orientate(). The parameter to _orientate changes the amount of rotation. The possible values are: [1, 2, 3] meaning a clockwise rotation of [90, 180, 270] degrees respectively. Once the image is rotated we call imlib_save_image() giving the filename of the new image to have Imlib2 save the rotated image.

With this example in your hands you should be able to quickly rotate images on 90 degree intervals using Imlib2.

Recipe: Image Flipping

dan 'dj2' sinclair

Imlib2 contains functions to do image flipping. This can be done either horizontally, vertically or diagonally. This recipe will show how to implement this functionality.

Example 2.5. Image Flipping

#define X_DISPLAY_MISSING

#include <Imlib2.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char ** argv) {
    Imlib_Image in_img;
    int dir = 0;

    if (argc != 4) {
        fprintf(stderr, "Usage: %s [in_img] [dir] [out_img]\n", argv[0]);
        return 1;
    }

    in_img = imlib_load_image(argv[1]);
    if (!in_img) {
        fprintf(stderr, "Unable to load %s\n", argv[1]);
        return 1;
    }
    imlib_context_set_image(in_img);

    dir = atoi(argv[2]);
    switch(dir) {
        case HORIZONTAL:
            imlib_image_flip_horizontal();
            break;

        case VERTICAL:
            imlib_image_flip_vertical();
            break;

        case DIAGONAL:
            imlib_image_flip_diagonal();
            break;

        default:
            fprintf(stderr, "Unknown value\n");
            return 1;
    }
    imlib_save_image(argv[3]);
    return 0;
}

This example does some minimal argument checking to begin, then loads the input image using the imlib_load_image() function, passing the filename to load. imlib_load_image() will either return the Imlib_Image object, or NULL if the load fails. Once we have the image object we set it as the current context image with a call to imlib_context_set_image(). This tells Imlib2 that this is the image we want to work with and all Imlib2 operations will work with this image. With the image context setup we decide on the type of flip we want to preform. This is done with one of the calls: imlib_image_flip_horizontal(), imlib_image_flip_vertical(), and imlib_image_flip_diagonal(). The diagonal flip essentially grabs the top left corner and makes it the bottom right corner. The top right becoming the bottom left. Once the image is flipped we call imlib_save_image() giving it the new filename and we're done.

This should give an example of image flipping with Imlib2. It will need enhancements before being put into a real app but the base is there.

Chapter 3. EVAS

Evas is a hardware-accelerated canvas API for X-Windows that can draw anti-aliased text, smooth super and sub-sampled images, alpha-blend, as well as drop down to using normal X11 primitives such as pixmaps, lines and rectangles for speed if your CPU or graphics hardware are too slow.

Evas abstracts any need to know much about the characteristics of your XServer's display, what depth or what magic visuals, it has. The most you need to tell Evas is how many colors (at a maximum) to use if the display is not a truecolor display. By default it is suggested to use 216 colors (as this equates to a 6x6x6 color cube - exactly the same color cube Netscape, Mozilla, gdkrgb etc. use so colors will be shared). If Evas can't allocate enough colors it keeps reducing the size of the color cube until it reaches plain black and white. This way, it can display on anything from a black and white only terminal to 16 color VGA to 256 color and all the way up through 15, 16, 24 and 32bit color.

Recipe: Using Ecore_Evas to simplify X11 canvas initialization

Ben technikolor Rockwood

Evas is a powerful and simple library to use, but before it can establish a canvas a X11 drawable needs to be setup. Manually setting up X11 can be a messy and frustrating task which detracts from concentrating on what you really want to do: develope an Evas application. But all this can be avoided by using the Ecore_Evas module of Ecore to do all the hard work for you.

The following example is a basic template that can be used as a starting point for any Evas application you want to build, significantly cutting down development time.

Example 3.1. Ecore_Evas Template

#include <Ecore_Evas.h>
#include <Ecore.h>

#define WIDTH 400
#define HEIGHT 400

        Ecore_Evas  *   ee;
        Evas        *   evas;
        Evas_Object *   base_rect;

int main(){

        ecore_init();

   ee = ecore_evas_software_x11_new(NULL, 0,  0, 0, WIDTH, HEIGHT);
        ecore_evas_title_set(ee, "Ecore_Evas Template");
        ecore_evas_borderless_set(ee, 0);
        ecore_evas_show(ee);


   evas = ecore_evas_get(ee);
        evas_font_path_append(evas, "fonts/");


   base_rect = evas_object_rectangle_add(evas);
        evas_object_resize(base_rect, (double)WIDTH, (double)HEIGHT);
        evas_object_color_set(base_rect, 244, 243, 242, 255);
        evas_object_show(base_rect);

        /* Insert Object Here */

        ecore_main_loop_begin();

        return 0;
}

Full details on Ecore_Evas can be found in the Ecore chapter of this book, but this basic template should get you playing with Evas right away. The important calls to note are ecore_evas_borderless_set() which defines whether the Evas window is windowed by your window manager or borderless, and evas_font_path_append() which defines the font path(s) used by your Evas app.

Recipe: Key Binds, using EVAS Key Events

Ben technikolor Rockwood

Many applications can benifit from providing key binds for commonly used operations. Whether accepting text in ways the EFL doesn't normally expect or just a way to bind the + key to raise the volume of a mixer, keybinds can add just the bit of functionality that makes your app a hit.

The following code is a simple and complete application that is useful in exploring keybinds using EVAS event callbacks. It creates a black 100 by 100 pixel window in which you can hit keys.

Example 3.2. Key grabbing using EVAS Events

#include <Ecore_Evas.h>
#include <Ecore.h>

#define WIDTH 100
#define HEIGHT 100

        Ecore_Evas  *   ee;
        Evas        *   evas;
        Evas_Object *   base_rect;

static int main_signal_exit(void *data, int ev_type, void *ev)
{
   ecore_main_loop_quit();
   return 1;
}

void key_down(void *data, Evas *e, Evas_Object *obj, void *event_info) {
        Evas_Event_Key_Down *ev;

        ev = (Evas_Event_Key_Down *)event_info;
        printf("You hit key: %s\n", ev->keyname);
}

int main(){
        ecore_init();
        ecore_event_handler_add(ECORE_EVENT_SIGNAL_EXIT, 
			main_signal_exit, NULL);

   ee = ecore_evas_software_x11_new(NULL, 0,  0, 0, WIDTH, HEIGHT);
        ecore_evas_title_set(ee, "EVAS KeyBind Example");
        ecore_evas_borderless_set(ee, 0);
        ecore_evas_show(ee);

   evas = ecore_evas_get(ee);

   base_rect = evas_object_rectangle_add(evas);
        evas_object_resize(base_rect, (double)WIDTH, (double)HEIGHT);
        evas_object_color_set(base_rect, 0, 0, 0, 255);
        evas_object_focus_set(base_rect, 1);
        evas_object_show(base_rect);

        evas_object_event_callback_add(base_rect, 
			EVAS_CALLBACK_KEY_DOWN, key_down, NULL);      

        ecore_main_loop_begin();
        
        ecore_evas_shutdown();
        ecore_shutdown();

        return 0;
}

You can compile this example in the following manner:

Example 3.3. EVAS Keybind Compile

gcc `evas-config --libs --cflags` `ecore-config --libs --cflags` \
> key_test.c -o key_test

In this example the canvas is setup in the usual way using Ecore_Evas to do the dirty work. The real magic occurs in the evas_object_event_callback_add() callback.

        evas_object_event_callback_add(base_rect,
                        EVAS_CALLBACK_KEY_DOWN, key_down, NULL);

By adding a callback to the base_rect, which is acting as the canvas background, we can execute a function (key_down() in this case) whenever we encounter a key down event, defined in Evas.h as EVAS_CALLBACK_KEY_DOWN.

There is one very important thing to do in addition to defining the callback: setting the focus. The evas_object_focus_set() function sets the focus on a given Evas object. It is the object that has focus that will actually accept the events, even though you explicitly define the Evas object that the callback is attached to. And only one object can be focused at a time. The most common problem encountered with Evas callbacks is forgetting to set focus.

void key_down(void *data, Evas *e, Evas_Object *obj, void *event_info) {
        Evas_Event_Key_Down *ev;

        ev = (Evas_Event_Key_Down *)event_info;
        printf("You hit key: %s\n", ev->keyname);
}

The key_down() function is called anytime a key down event occurs after defining it's callback. The function declaration is that of a standard Evas callback (see Evas.h). The important piece of information we need to know is what key was pressed, which is contained in the Evas event_info structure. After setting up the Evas_Event_Key_Down structure for use as seen above we can access the element keyname to determine which key was pressed.

In most cases you'll likely use a switch or nested if's to define which keys do what, and it's recommended that this functionality be paired with an configuration EDB to provide centralization and easy expansion of your applications keybind settings.

Recipe: Evas Smart Object Introduction

dan 'dj2' sinclair

As you work with Evas more, you'll begin to have several Evas_Objects that you are working with and applying the same operations to keep them in sync. It would be much more convenient to group all of these separate Evas_Objects into a single object that the transforms can be applied too.

Evas smart objects provide the ability to write your own objects and have Evas call your functions to do the moving, resizing, hiding, layer and all of those other things an Evas_Object is responsible to handle. Along with the normal Evas_Object callbacks, smart objects allow you to define your own functions for the object to handle any special operations you may require.

This introduction is broken into three files: foo.h, foo.c, and main.c. The smart object being created is called foo and is defined in the foo.[ch] files. main.c is there to show how the new smart object can be used.

The smart object itself is just two squares, one inside the other, with the inner one offset 10% from the edge of the outer square. As the main program executes an Ecore timer callback will reposition and resize the smart object.

The basic file for this smart object is from an Evas Smart Object template by Atmos located at: www.atmos.org/code/src/evas_smart_template_atmos.c which in turn was based off of a template by Rephorm.

First we need to define the external interface to our smart object. In this case we only need a call to create the new object.

Example 3.4. foo.h

#ifndef _FOO_H_
#define _FOO_H_ 
   
#include <Evas.h>

Evas_Object *foo_new(Evas *e);

#endif

With that out of the way, we get into the dark underbelly of the beast, the smart object code.

Example 3.5. foo.c

#include <Evas.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct _Foo_Object Foo_Object;
struct _Foo_Object {
	Evas_Object *clip;
	Evas_Coord x, y, w, h;

	Evas_Object *outer;
	Evas_Object *inner;
};  

The Foo_Object will store all the information we need for our object. In this case it is the outer box object, the inner box object, a clipping object and the current position and size of the object.

static Evas_Smart *_foo_object_smart_get();
static Evas_Object *foo_object_new(Evas *evas);
static void _foo_object_add(Evas_Object *o);
static void _foo_object_del(Evas_Object *o);
static void _foo_object_layer_set(Evas_Object *o, int l);
static void _foo_object_raise(Evas_Object *o);
static void _foo_object_lower(Evas_Object *o);
static void _foo_object_stack_above(Evas_Object *o, Evas_Object *above);
static void _foo_object_stack_below(Evas_Object *o, Evas_Object *below);
static void _foo_object_move(Evas_Object *o, Evas_Coord x, Evas_Coord y);
static void _foo_object_resize(Evas_Object *o, Evas_Coord w, Evas_Coord h);
static void _foo_object_show(Evas_Object *o);
static void _foo_object_hide(Evas_Object *o);
static void _foo_object_color_set(Evas_Object *o, int r, int g, int b, int a);
static void _foo_object_clip_set(Evas_Object *o, Evas_Object *clip);
static void _foo_object_clip_unset(Evas_Object *o);

The predeclarations required for the smart object. These will be explained as we come to there actual implementation.

Evas_Object *foo_new(Evas *e) {
    Evas_Object *result = NULL;
    Foo_Object *data = NULL;

    if ((result = foo_object_new(e))) {
        if ((data = evas_object_smart_data_get(result)))
            return result;
        else
            evas_object_del(result);
    }

    return NULL;
}

foo_new() is our one external interface and is responsible for setting up the object itself. The call to foo_object_new() will do all of the heavy lifting in the object creation. The evas_object_smart_data_get() is more of an error check then anything else. When the foo_object_new() runs it will add the smart object to the evas and this will result in an add call on the object. In our case this add call will create a Foo_Object. So, we're just making sure that the Foo_Object has been created.

static Evas_Object *foo_object_new(Evas *evas) {
    Evas_Object *foo_object;

    foo_object = evas_object_smart_add(evas, _foo_object_smart_get());
    return foo_object;
}

Our foo_object_new() function has the simple task of adding our smart object onto the given Evas. This is done through the evas_object_smart_add() passing the Evas and the Evas_Smart * object. Our Evas_Smart * is produced by the _foo_object_smart_get() call.

static Evas_Smart *_foo_object_smart_get() {
    static Evas_Smart *smart = NULL;
    if (smart)
        return (smart);

    smart = evas_smart_new("foo_object",
                            _foo_object_add,
                            _foo_object_del,
                            _foo_object_layer_set,
                            _foo_object_raise,
                            _foo_object_lower,
                            _foo_object_stack_above,
                            _foo_object_stack_below,
                            _foo_object_move,
                            _foo_object_resize,
                            _foo_object_show,
                            _foo_object_hide,
                            _foo_object_color_set,
                            _foo_object_clip_set,
                            _foo_object_clip_unset,
                            NULL
                          );
    return smart;
}

You'll notice that the Evas_Smart *smart in this function is declared static. This is because no matter how many Evas_Objects we create, there will be only one Evas_Smart object. As Raster put it, an Evas_Smart is like a C++ class definition, not an instance. The Evas_Object is the instance of the Evas_Smart.

The smart object itself is created through the call to evas_smart_new(). To this function we pass in the name of the smart object, all of the callback routines for the smart object, and any user data. In this case we don't have user data so we set it to NULL.

static void _foo_object_add(Evas_Object *o) {
    Foo_Object *data = NULL;
    Evas *evas = NULL;

    evas = evas_object_evas_get(o);

    data = (Foo_Object *)malloc(sizeof(Foo_Object));
    memset(data, 0, sizeof(Foo_Object));

    data->clip = evas_object_rectangle_add(evas);
    data->outer = evas_object_rectangle_add(evas);
    evas_object_color_set(data->outer, 0, 0, 0, 255);
    evas_object_clip_set(data->outer, data->clip);
    evas_object_show(data->outer);

    data->inner = evas_object_rectangle_add(evas);
    evas_object_color_set(data->inner, 255, 255, 255, 126);
    evas_object_clip_set(data->inner, data->clip);
    evas_object_show(data->inner);

    data->x = 0;
    data->y = 0;
    data->w = 0;
    data->h = 0;

    evas_object_smart_data_set(o, data);
}

When the call to evas_object_smart_add() is called in foo_object_new(), this function, _foo_object_add() will be called so we can setup any internal data for this smart object.

For this smart object we setup three internal Evas_Objects. Those being data->clip used for clipping the other two objects, data->outer our outer rectangle and data->inner our inner rectangle. The inner and outer rectangles have there clipping set to the clip object and are shown immediately. The clip object is not shown, it will be shown when the user calls evas_object_show() on this object.

Finally we call evas_object_smart_data_set() to set our new Foo_Object as data to this smart object. This data will be retrieved in the other functions of this object by calling evas_object_smart_data_get().

static void _foo_object_del(Evas_Object *o) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o))) {
        evas_object_del(data->clip);
        evas_object_del(data->outer);
        evas_object_del(data->inner);
        free(data);
    }
}

The _foo_object_del() callback will be executed if the user calls evas_object_del() on our object. For this object its as simple as evas_object_deling our three rectangles and freeing our Foo_Object data structure.

static void _foo_object_layer_set(Evas_Object *o, int l) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o))) {
        evas_object_layer_set(data->clip, l);
    }
}

static void _foo_object_raise(Evas_Object *o) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o))) {
        evas_object_raise(data->clip);
    }
}

static void _foo_object_lower(Evas_Object *o) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o))) {
        evas_object_lower(data->clip);
    }
}

static void _foo_object_stack_above(Evas_Object *o, Evas_Object *above) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o))) {
        evas_object_stack_above(data->clip, above);
    }
}

static void _foo_object_stack_below(Evas_Object *o, Evas_Object *below) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o))) {
        evas_object_stack_below(data->clip, below);
    }
}

This group of functions: _foo_object_layer_set(), _foo_object_raise(), _foo_object_lower(), _foo_object_stack_above(), and _foo_object_stack_below() all work in the same fashion, applying the required evas_object_* function on the data->clip object.

These functions are triggered through the use of: evas_object_layer_set(), evas_object_raise(), evas_object_lower(), evas_object_stack_above(), and evas_object_stack_below() respectively.

static void _foo_object_move(Evas_Object *o, Evas_Coord x, Evas_Coord y) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o))) {
        float ix, iy;
        ix = (data->w - (data->w * 0.8)) / 2;
        iy = (data->h - (data->h * 0.8)) / 2;

        evas_object_move(data->clip, x, y);
        evas_object_move(data->outer, x, y);
        evas_object_move(data->inner, x + ix, y + iy);

        data->x = x;
        data->y = y;
    }
}

The _foo_object_move() callback will be triggered when evas_object_move() is called on our object. Each of the internal objects is moved into its correct positioning with calls to evas_object_move().

static void _foo_object_resize(Evas_Object *o, Evas_Coord w, Evas_Coord h) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o))) {
        float ix, iy, iw, ih;
        iw = w * 0.8;
        ih = h * 0.8;

        ix = (w - iw) / 2;
        iy = (h - iw) / 2;

        evas_object_resize(data->clip, w, h);
        evas_object_resize(data->outer, w, h);

        evas_object_move(data->inner, data->x + ix, data->y + iy);
        evas_object_resize(data->inner, iw, ih);

        data->w = w;
        data->h = h;
    }
}

The _foo_object_resize() calback will be triggered when the user calls evas_object_resize() on our object. So, for our object, we need to resize data->clip and data->outer to the full size available for our object. This is done with the evas_object_resize() calls. We then need to move and resize the data->inner object so it stays in the correct positioning in the outer rectangle. These are done with the evas_object_move() and evas_object_resize() respectively. We then store the current width and height back into our object so we can reference them later.

static void _foo_object_show(Evas_Object *o) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o)))
            evas_object_show(data->clip);
}

The _foo_object_show() callback will be triggered when evas_object_show() is called on our object. In order to show our object all we need to do is show the clip region as our actual rectangles are clipped to it. This is done through the call to evas_object_show().

static void _foo_object_hide(Evas_Object *o) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o)))
        evas_object_hide(data->clip);
}

The _foo_object_hide() callback will be triggered when a call to evas_object_hide() is made on our object. Because we are using in internal clipping object we just need to hide the clip object, data->clip, to hide our smart object. This is done through the call to evas_object_hide().

static void _foo_object_color_set(Evas_Object *o, int r, int g, int b, int a) {
}

The _foo_object_color_set() function will be called when evas_object_color_set() is called on our Evas_Object. But, since I don't want my object to change colours, I ignore this callback.

static void _foo_object_clip_set(Evas_Object *o, Evas_Object *clip) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o)))
        evas_object_clip_set(data->clip, clip);
}

The _foo_object_clip_set() callback will be triggered when the evas_object_clip_set() call is made on our object. In this case we propagate this setting to our internal clipping object, data->clip through the call to evas_object_clip_set().

static void _foo_object_clip_unset(Evas_Object *o) {
    Foo_Object *data;

    if ((data = evas_object_smart_data_get(o)))
        evas_object_clip_unset(data->clip);
}

The _foo_object_clip_unset() callback will be triggered when a call to evas_object_clip_unset() is made on our object. We just remove the clip set to our internal clipping object through the call to evas_object_clip_unset().

Once the smart object code is complete we can create our main program to utilize the new smart object.

Example 3.6. main.c

#include <stdio.h>
#include <Ecore_Evas.h>
#include <Ecore.h>
#include "foo.h"

#define WIDTH 400
#define HEIGHT 400
#define STEP 10

static int dir = -1;
static int cur_width = WIDTH;
static int cur_height = HEIGHT;

static int timer_cb(void *data);

int main() {
    Ecore_Evas  *ee;
    Evas        *evas;
    Evas_Object *o;

    ecore_init();

    ee = ecore_evas_software_x11_new(NULL, 0, 0, 0, WIDTH, HEIGHT);
    ecore_evas_title_set(ee, "Smart Object Example");
    ecore_evas_borderless_set(ee, 0);
    ecore_evas_show(ee);

    evas = ecore_evas_get(ee);

    o = evas_object_rectangle_add(evas);
    evas_object_resize(o, (double)WIDTH, (double)HEIGHT);
    evas_object_color_set(o, 200, 200, 200, 255);
    evas_object_layer_set(o, -255);
    evas_object_show(o);

    o = foo_new(evas);
    evas_object_move(o, 0, 0);
    evas_object_resize(o, (double)WIDTH, (double)HEIGHT);
    evas_object_layer_set(o, 0);
    evas_object_show(o);

    ecore_timer_add(0.1, timer_cb, o);
    ecore_main_loop_begin();

    return 0;
}   
        
static int timer_cb(void *data) {
    Evas_Object *o = (Evas_Object *)data;
    Evas_Coord x, y;

    cur_width += (dir * STEP);
    cur_height += (dir * STEP);

    x = (WIDTH - cur_width) / 2;
    y = (HEIGHT - cur_height) / 2;

    if ((cur_width < STEP) || (cur_width > (WIDTH - STEP)))
        dir *= -1;

    evas_object_move(o, x, y);
    evas_object_resize(o, cur_width, cur_height);
    return 1;
}

Most of this program is the same as that given in the using Ecore_Evas recipe given earlier. The creation of our new smart object is seen in the code snippet:

    o = foo_new(evas);
    evas_object_move(o, 0, 0);
    evas_object_resize(o, (double)WIDTH, (double)HEIGHT);
    evas_object_layer_set(o, 0);
    evas_object_show(o);

Once your new foo_new() returns our object we can manipulate it with the normal Evas calls, so we proceed to set it position, size, layer and then show the object.

Once the new smart object is created and shown we setup an Ecore timer to trigger every 0.1 seconds. When the timer is triggered it will execute the function timer_cb(). This callback function will either shrink, or grow the size of our smart object while keeping it centered in the main window.

Compiling this example is as simple as:

Example 3.7. Compilation

zero@oberon [evas_smart] -> gcc -o foo foo.c main.c \
    `ecore-config --cflags --libs` `evas-config --cflags --libs`

Evas smart objects are simple to create but provide a powerful mechanism to abstract out pieces of your program. To see some more smart objects take a look at any of the Esmart objects, Etox or Emotion.

Chapter 4. Ecore

What is Ecore? Ecore is the core event abstraction layer and X abstraction layer that makes doing selections, Xdnd, general X stuff, and event loops, timeouts and idle handlers fast, optimized, and convenient. It's a separate library so anyone can make use of the work put into Ecore to make this job easy for applications.

Ecore is completely modular. At its base is the event handlers and timers, and initialization and shutdown functions. The abstraction modules for Ecore include:

  • Ecore X

  • Ecore FB

  • Ecore EVAS

  • Ecore TXT

  • Ecore Job

  • Ecore IPC

  • Ecore Con

  • Ecore Config

Ecore is so modular and powerful that it can be extremely useful even in non-graphics programing by itself. As an example, several web servers have been written that were based solely on Ecore and the Ecore_Con module for abstract socket communication.

Recipe: Ecore Config Introduction

dan 'dj2' sinclair

The Ecore_Config module provides the programmer with a very simple way to setup configuration files for their program. This recipe will give an example of how to integrate the beginnings of Ecore_Config into your program and use it to get configuration data.

Example 4.1. Simple Ecore_Config program

#include <Ecore_Config.h>

int main(int argc, char ** argv) {
    int i;
    float j;
    char *str;

    if (ecore_config_init("foo") != ECORE_CONFIG_ERR_SUCC) {
        printf("Cannot init Ecore_Config");
        return 1;
    }

    ecore_config_int_default("/int_example", 1);
    ecore_config_string_default("/this/is/a/string/example", "String");
    ecore_config_float_default("/float/example", 2.22);

    ecore_config_load();

    i = ecore_config_int_get("/int_example");
    str = ecore_config_string_get("/this/is/a/string/example");
    j = ecore_config_float_get("/float/example");

    printf("str is (%s)\n", str);
    printf("i is (%d)\n", i);
    printf("j is (%f)\n", j);

    free(str);

    ecore_config_shutdown();
    return 0;
}

As you can see from this example the basic usage of Ecore_Config is simple. The system is initialized with a call to ecore_config_init. The program name setting control where Ecore_Config will look for your configuration database. The directory and file name are: ~/.e/apps/PROGRAM_NAME/config.db.

For each configuration variable you are getting from Ecore_Config, you can assign a default value in the case that the user does not have a config.db file. The defaults are assigned with the ecore_config_*_default where * is one of the Ecore_Config types. The first parameter is the key under which this is to be accessed. These keys must be unique over your program. The value passed is of the type appropriated for this call.

The ecore_config_load call will read the values from the config.db file into Ecore_Config. After which we can access the files with the ecore_config_*_get methods (again * is the type of data desired). These routines take the key name for this item and return the value associated with that key. Each function returns a type that corresponds to the function call name.

ecore_config_shutdown is then called to shutdown the Ecore_Config system before the program exits.

Example 4.2. Compilation command

gcc -o ecore_config_example ecore_config_example.c `ecore-config --cflags --libs`

To compile the program you can use the ecore-config script to get all of the required linking and library information for Ecore_Config. If you run this program as is you will receive the values put into ecore_config as the defaults as output. Once you know the program is working, you can create a simple config.db file to read the values.

Example 4.3. Simple config.db script (build_cfg_db.sh)

#!/bin/sh

DB=config.db

edb_ed $DB add /int_example int 2
edb_ed $DB add /this/is/a/string/example str "this is a string"
edb_ed $DB add /float/example float 42.10101

When build_cfg_db.sh is executed it will create a config.db file in the current directory. This file can then be copied into ~/.e/apps/PROGRAM_NAME/config.db where PROGRAM_NAME is the value passed into ecore_config_init. Once the file is copied in place, executing the test program again will show the values given in the config file instead of the defaults. You can specify as many, or as few of the configuration keys in the config file and Ecore_Config will either show the user value or the default value.

Recipe: Ecore Config Listeners

dan 'dj2' sinclair

When using Ecore Config to handle the configuration of your application it is nice to be able to be notified when that configuration has been changed. This is accomplished through the use of listeners in Ecore_Config.

Example 4.4. Ecore_Config listener

#include <Ecore.h>
#include <Ecore_Config.h>

static int listener_cb(const char *key, const Ecore_Config_Type type,
            const int tag, void *data);

enum {
    EX_ITEM,
    EX_STR_ITEM,
    EX_FLOAT_ITEM
};

int main(int argc, char ** argv) {
    int i;
    float j;
    char *str;

    if (!ecore_init()) {
        printf("Cannot init ecore");
        return 1;
    }

    if (ecore_config_init("foo") != ECORE_CONFIG_ERR_SUCC) {
        printf("Cannot init Ecore_Config");
        ecore_shutdown();
        return 1;
    }

    ecore_config_int_default("/int/example", 1);
    ecore_config_string_default("/string/example", "String");
    ecore_config_float_default("/float/example", 2.22);

    ecore_config_listen("int_ex", "/int/example", listener_cb, 
                                                EX_ITEM, NULL);
    ecore_config_listen("str_ex", "/string/example", listener_cb, 
                                                EX_STR_ITEM, NULL);
    ecore_config_listen("float_ex", "/float/example", listener_cb, 
                                                EX_FLOAT_ITEM, NULL);

    ecore_main_loop_begin();
    ecore_config_shutdown();
    ecore_shutdown();
    return 0;
}

static int listener_cb(const char *key, const Ecore_Config_Type type,
                                            const int tag, void *data) {

    switch(tag) {
        case EX_ITEM:
            {
                int i = ecore_config_int_get(key);
                printf("int_example :: %d\n", %i);
            }
            break;

        case EX_STR_ITEM:
            {
                char *str = ecore_config_string_get(key);
                printf("str :: %s\n", %str);
                free(str);
            }
            break;

        case EX_FLOAT_ITEM:
            {
                float f = ecore_config_float_get(key);
                printf("float :: %f\n", %f);
            }
            break;

        default:
            printf("Unknown tag (%d)\n", tag);
            break;
    }
}

Ecore_Config is setup in the usual way, and we create some default keys as happens normally. The interesting parts come into play with the calls to ecore_config_listen(). This is the call that tells Ecore_Config we want to be notified of configuration changes. ecore_config_listen() takes five parameters:

  • name

  • key

  • listener callback

  • id tag

  • user data

The name field is a name string given by you to identify this listener callback. The key is the name of the key you wish to listen on, this will be the same as the key name given in the _default calls above. The listener callback is the calback function to be executed on change. The id tag is a numeric tag that can be given to each listener and will be passed to the calback function. Finally, user data is any data you wish to have passed to the callback when it is executed.

The callback function has a signature similar to:

int listener_cb(const char *key, const Ecore_Config_Type type,
                                    const int tag, void *data);

The key is the key name that was being listen on. The type parameter will contain the Ecore_Config type. This can be one of:

PT_NIL

Property with no value

PT_INT

Integer property

PT_FLT

Float property

PT_STR

String property

PT_RGB

Colour property

PT_THM

Theme property

The tag parameter is the value that was given in the listener creation call above. Finally, the data is any user data attached to the listener when it was created.

If you wish to remove the listener at a later date ecore_config_deaf() is provided. This takes three parameters:

  • name

  • key

  • listener callback

Each of these parameters corresponds to the parameter given in the initial ecore_config_listen() call.

Example 4.5. Compilation

zero@oberon [ecore_config] -> gcc -o ecfg ecfg_listener.c \
    `ecore-config --cflags --libs`

If you execute the program you will see the default values printed back to the screen. If you now launch examine as follows:

zero@oberon [ecore_config] -> examine foo

(foo is the name given to ecore_config_init()). You should then be able to modify the settings to the application and, after pressing 'save', see the modified values printed back to the console.

Recipe: Ecore Ipc Introduction

dan 'dj2' sinclair

The Ecore_Ipc library provides a robust and efficient wrapper around the Ecore_Con module. Ecore_Ipc allows you to set up your server communications and handles all of the tricky stuff under the hood. This recipe will give a simple example of an Ecore client and an Ecore server.

When working with Ecore_Ipc, when writing a client or a server app an Ecore_Ipc_Server object will be created. This is because in either case it is a server being manipulated, either the one being setup, or the one being communicated with. After that, everything is easy.

Example 4.6. Ecore_Ipc client: preamble

#include <Ecore.h>
#include <Ecore_Ipc.h>

int sig_exit_cb(void *data, int ev_type, void *ev);
int handler_server_add(void *data, int ev_type, void *ev);
int handler_server_del(void *data, int ev_type, void *ev);
int handler_server_data(void *data, int ev_type, void *ev);

The Ecore.h file is included so we can have access to the exit signal type. The functions will be explained later when their callbacks are hooked up.

Example 4.7. Ecore_Ipc client: main setup

int main(int argc, char ** argv) {
    Ecore_Ipc_Server *server;

    if (!ecore_init()) {
        printf("unable to init ecore\n");
        return 1;
    }

    if (!ecore_ipc_init()) {
        printf("unable to init ecore_con\n");
        ecore_shutdown();
        return 1;
    }
    ecore_event_handler_add(ECORE_EVENT_SIGNAL_EXIT, sig_exit_cb, NULL);

As mentioned earlier, even though we are writing a client app, we still use an Ecore_Ipc_Server object. Using Ecore_Ipc requires the setup of Ecore itself. This is done with a simple call to ecore_init. Ecore_Ipc is then setup with a call to ecore_ipc_init. If either of these return 0, the appropriate action is taken to undo any initialization take to this point. The ECORE_EVENT_SIGNAL_EXIT callback is hooked up so we can exit gracefully if required.

Example 4.8. Ecore_Ipc client: main creating client

    server = ecore_ipc_server_connect(ECORE_IPC_REMOTE_SYSTEM, 
                                        "localhost", 9999, NULL);
    ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_ADD, 
                                        handler_server_add, NULL);
    ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_DEL, 
                                        handler_server_del, NULL);
    ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_DATA, 
                                        handler_server_data, NULL);

In this example we are creating a remote connection to the server named "localhost" on the port 9999. This is done with the ecore_ipc_server_connect method. The first parameter is the type of connection being made, which can be one of: ECORE_IPC_REMOTE_SYSTEM, ECORE_IPC_LOCAL_SYSTEM, or ECORE_IPC_LOCAL_USER. If OpenSSL was available when Ecore_Ipc was compiled, ECORE_IPC_USE_SSL can be or'd with the connection type to create an SSL connection.

The three calls to ecore_event_handler_add setup the callbacks for the different types of events we will be receiving from the server. A server was added, a server was deleted, or the server sent us data.

Example 4.9. Ecore_Ipc client: main end

    ecore_ipc_server_send(server, 3, 4, 0, 0, 0, "Look ma, no pants", 17);

    ecore_main_loop_begin();

    ecore_ipc_server_del(server);
    ecore_ipc_shutdown();
    ecore_shutdown();
    return 0;
}

For the purposes of this example, the client is sending a message on startup to the server, which the server will respond to. The client message is sent with the ecore_ipc_server_send command. ecore_ipc_server_send takes the server to send to, the message major, message minor, a reference, a reference to, a response, the data and a size. These parameters, except for the server are up the the client and can refer to anything required. This hopefully gives the maximum flexibility in creating client/server IPC apps.

After the server message is sent we enter into the main ecore loop and wait for events. If the main loop is exited we delete the server object, shutdown Ecore_Ipc with a call to ecore_ipc_shutdown, and shutdown ecore through ecore_shutdown.

Example 4.10. Ecore_Ipc client: sig_exit_cb

int sig_exit_cb(void *data, int ev_type, void *ev) {
    ecore_main_loop_quit();
    return 1;
}

The sig_exit_cb just tells ecore to quit the main loop. This isn't strictly necessary because if the only thing being done is calling ecore_main_loop_quit(), Ecore will handing this itself if there is not handler setup. But this shows how a handler can be created if one is needed for your application.

Example 4.11. Ecore_Ipc client: the callbacks

int handler_server_add(void *data, int ev_type, void *ev) {
    Ecore_Ipc_Event_Server_Add *e = (Ecore_Ipc_Event_Server_Add *)ev;
    printf("Got a server add %p\n", e->server);
    return 1;
}

int handler_server_del(void *data, int ev_type, void *ev) {
    Ecore_Ipc_Event_Server_Del *e = (Ecore_Ipc_Event_Server_Del *)ev;
    printf("Got a server del %p\n", e->server);
    return 1;
}

int handler_server_data(void *data, int ev_type, void *ev) {
    Ecore_Ipc_Event_Server_Data *e = (Ecore_Ipc_Event_Server_Data *)ev;
    printf("Got server data %p [%i] [%i] [%i] (%s)\n", e->server, e->major,
                                e->minor, e->size, (char *)e->data);
    return 1;
}

These three callbacks, handler_server_add, handler_server_del, and handler_server_data are body of the client handling all events related to the server we are connected to. Each of the callbacks has an associated event structure, Ecore_Ipc_Event_Server_Add, Ecore_Ipc_Event_Server_Del and Ecore_Ipc_Event_Server_Data containing information on the event itself.

When we first connect to the server the handler_server_add callback will be executed allowing any setup to be accomplished.

If the server breaks the connection the handler_server_del callback will be executed allowing any required cleanup.

When the server sends data to the client the handler_server_data callback will the executed. Which in this example just prints some information about the message itself and the message body.

And thats the client. The code itself is pretty simple thanks to the abstractions provided by Ecore.

Example 4.12. Ecore_Ipc server: preamble

#include <Ecore.h>
#include <Ecore_Ipc.h>

int sig_exit_cb(void *data, int ev_type, void *ev);
int handler_client_add(void *data, int ev_type, void *ev);
int handler_client_del(void *data, int ev_type, void *ev);
int handler_client_data(void *data, int ev_type, void *ev);

As with the client, the Ecore.h header is included to get access the to the exit signal. The Ecore_Ipc.h header is required for apps making use of the Ecore_Ipc library. Each sig handler will be explained with its code.

Example 4.13. Ecore_Ipc server: main setup

int main(int argc, char ** argv) { 
    Ecore_Ipc_Server *server;
    
    if (!ecore_init()) {
        printf("Failed to init ecore\n");
        return 1;
    }
    
    if (!ecore_ipc_init()) {
        printf("failed to init ecore_con\n");
        ecore_shutdown();
        return 1;
    }
    
    ecore_event_handler_add(ECORE_EVENT_SIGNAL_EXIT, sig_exit_cb, NULL);

This is the same as the client setup above.

Example 4.14. Ecore_Ipc server: main creating server

    server = ecore_ipc_server_add(ECORE_IPC_REMOTE_SYSTEM, "localhost", 9999, NULL);
    ecore_event_handler_add(ECORE_IPC_EVENT_CLIENT_ADD, handler_client_add, NULL);
    ecore_event_handler_add(ECORE_IPC_EVENT_CLIENT_DEL, handler_client_del, NULL);
    ecore_event_handler_add(ECORE_IPC_EVENT_CLIENT_DATA, handler_client_data, NULL);

Unlike the client, for the server we add a listener to port 9999 on the machine "localhost" through the call ecore_ipc_server_add. This will create and return the server object to us. We then hook in the required signal handlers, the difference to the client being we want CLIENT events this time instead of SERVER events.

Example 4.15. Ecore_Ipc client: main end

    ecore_main_loop_begin();

    ecore_ipc_server_del(server);
    ecore_ipc_shutdown();
    ecore_shutdown();
    return 0;
}

This again is identical to the client shutdown, minus the sending of data to the server.

Example 4.16. Ecore_Ipc server: sig_exit callback

The sig_exit_cb is again identical to that seen in the client.

Example 4.17. Ecore_Ipc server: the callbacks

int handler_client_add(void *data, int ev_type, void *ev) {
    Ecore_Ipc_Event_Client_Add *e = (Ecore_Ipc_Event_Client_Add *)ev;
    printf("client %p connected to server\n", e->client);
    return 1;
}

int handler_client_del(void *data, int ev_type, void *ev) {
    Ecore_Ipc_Event_Client_Del *e = (Ecore_Ipc_Event_Client_Del *)ev;
    printf("client %p disconnected from server\n", e->client);
    return 1;
}

int handler_client_data(void *data, int ev_type, void *ev) {
    Ecore_Ipc_Event_Client_Data *e = (Ecore_Ipc_Event_Client_Data *)ev;
    printf("client %p sent [%i] [%i] [%i] (%s)\n", e->client, e->major,
                                e->minor, e->size, (char *)e->data);
    
    ecore_ipc_client_send(e->client, 3, 4, 0, 0, 0, "Pants On!", 9);
    return 1;
}

The event callbacks are similar to those seen in the client app. The main difference is that the events are _Client_ events instead of _Server_ events.

The add callback is when a client connects to our server, with the del callback being its opposite when the client disconnects. The data callback is for when a client sends data to the server.

At the end of the handler_client_data callback we do a call to ecore_ipc_client_send. This sends data to the client. As with sending data to the server, the parameters are: the client to send to, major number, minor number, reference, reference to, response, data and the data size.

Example 4.18. Ecore_Ipc: compilation

CC = gcc
    
all: server client

server: server.c
    $(CC) -o server server.c `ecore-config --cflags --libs`
    
client: client.c
    $(CC) -o client client.c `ecore-config --cflags --libs`

clean:
    rm server client

As with other ecore apps, it is very easy to compile an Ecore_Ipc app. As long as your Ecore was compiled with Ecore_Ipc, simply invoking the 'ecore-config --cflags --libs' command will add all of the required library paths and linker information.

As seen in this example, Ecore_Ipc is an easy to use library to create client/server apps.

Recipe: Ecore Timers

dan 'dj2' sinclair

If you need to have a callback triggered at a specific time, with the possibility of repeating the timer continuously, then the Ecore_Timer is what you are looking for.

Example 4.19. Ecore Timers

#include <stdio.h>
#include <Ecore.h>

static int timer_one_cb(void *data);
static int timer_two_cb(void *data);

int main(int argc, char ** argv) {
    ecore_init();

    ecore_timer_add(1, timer_one_cb, NULL);
    ecore_timer_add(0.5, timer_two_cb, NULL);

    ecore_main_loop_begin();
    ecore_shutdown();

    return 0;
}

static int timer_one_cb(void *data) {
    printf("1");
    fflush(stdout);
    return 1;
}

static int timer_two_cb(void *data) {
    printf("2");
    fflush(stdout);
    return 1;
}

The creation of the timers is as simple as calling ecore_timer_add(). This will return an Ecore_Timer strut on success or NULL on failure. In this case I'm ignoring the return value. The three parameters are:

  • double timeout

  • int (*callback)(void *data)

  • const void *user_data

The timeout gives the number of seconds in which this timer will expire. In the case of this example we give it 1 second and 0.5 seconds respectively. The callback function is the one that will be executed when the timer expires and the user_data is any data to be passed to the callback function.

The callback functions all have the same signature int callback(void *data). The return value of the timer should be either 0 or 1. If you return 0 the timer will expire and will not be run again. If you return 1, the timer will be rescheduled to re-execute in the amount of time given by the timeout. This allows you to activate or continue the timer as required by your program.

If you have a timer that you wish to remove at some point in the future you can call ecore_timer_del(Ecore_Timer *). If this delete succeeds the pointer will be returned otherwise NULL will be returned. After calling the delete function the Ecore_Timer structure will be invalid and you should not use it again in your program.

Compiling the example is as simple as:

Example 4.20. Compilation

gcc -Wall -o etimer etimer.c `ecore-config --cflags --libs`

If you run the program you should see a series of '1's and '2's on the screen with twice as many '2's as '1's.

The Ecore_Timers are easy to setup and use and provide a powerful timing mechanism to your programs.

Chapter 5. EDB & EET

EDB is a database convenience library wrapped around the Berkeley DB 2.7.7 by Sleepycat Software. It is intended to make accessing database information portable, easy, fast and efficient.

EET is a tiny library designed to write arbitary chunks of data to a file and optionally compress each chunk (very much like a zip file) and allows for fast random-access reading of the file later on. It does not do zip as a zip itself has more complexity than is needed, and it was much simpler to impliment this once here.

EDB provides an excellent method of storing and retrieving application configuration information, although it can be used for more extensively than that. Ebits, the predecessor to Edje, even used EDB as a container for Ebits themes prior to EET. An Edb consists of a series of key/value pairs, which can consist of a variety of data types, including integers, floating point values, strings, and binary data. The simplified API provides simple, complete, and unified functions for managing and accessing your database.

In addition to the library, a variety of tools are available to access and modify your EDBs. The edb_ed tool provides a simple command line interface that can easily be scripted, especially useful for use with the GNU autotools suite. The edb_vt_ed tool provides an easy to use curses interface. Finally, edb_gtk_ed provides an elegant and easy GUI interface, especially useful for end user editing of configuration data contained in EDBs.

Eet is extremely fast, small and simple. Eet files can be very small and highly compressed, making them very optimal for just sending across the internet without having to archive, compress or decompress and install them. They allow for lightning-fast random-acess reads once created, making them perfect for storing data that is written once (or rarely) and read many times, but the program does not want to have to read it all in at once.

It also can encode and decode data structures in memory, as well as image data for saving to Eet files or sending across the network to other machines, or just writing to arbitary files on the system. All data is encoded in a platform independant way and can be written and read by any architecture.

Recipe: Creating EDB files from the shell

dan 'dj2' sinclair

It is often desired to create the EDB files from a simple shell script, it can then be made part of the build process.

This can easily be accomplished by using the edb_ed program. edb_ed is a very simple interface into EDB, allowing you to create/edit/delete keys/value pairs inside of EDB databases.

Example 5.1. EDB file shell script

#!/bin/sh

DB=out.db

edb_ed $DB add /global/debug_lvl int 2
edb_ed $DB add /foo/theme str "default"
edb_ed $DB add /bar/number_of_accounts int 1
edb_ed $DB add /nan/another float 2.3

If the output file does not exist the first time an add command is called, then edb_ed will create the file and do any required setup. The add is used to add entries into the DB. The first parameter after add is the key that the data will be inserted into the DB with. This key will be used to look up the data by your application in the future. The next parameter is the type of data to be added. This can be one of:

  • int

  • str

  • float

  • data

The last parameter is the value that is to be associated with this key.

Using edb_ed you can quickly and easily create/edit/view any EDB files required for your application.

Recipe: EDB introduction

dan 'dj2' sinclair

EDB provides a powerful database backend for use in your application. This recipe is a simple introduction that will open a database, write several keys and then read them back out.

Example 5.2. EDB introduction

#include <stdio.h>
#include <Edb.h>

#define INT_KEY     "/int/val"
#define STR_KEY     "/str/val"
#define FLT_KEY     "/float/val"

int main(int argc, char ** argv) {
    E_DB_File *db_file = NULL;
    char *str;
    int i;
    float f;

    if (argc < 2) {
        printf("Need db file\n");
        return 0;
    }

    db_file = e_db_open(argv[1]);
    if (db_file == NULL) {
        printf("Error opening db file (%s)\n", argv[1]);
        return 0;
    }

    printf("Adding values...\n");
    e_db_int_set(db_file, INT_KEY, 42);
    e_db_float_set(db_file, FLT_KEY, 3.14159);
    e_db_str_set(db_file, STR_KEY, "My cats breath smells like...");

    printf("Reading values...\n");
    if (e_db_int_get(db_file, INT_KEY, &i))
        printf("Retrieved (%s) with value (%d)\n", INT_KEY, i);

    if (e_db_float_get(db_file, FLT_KEY, &f))
        printf("Retrieved (%s) with value (%f)\n", FLT_KEY, f);

    if ((str = e_db_str_get(db_file, STR_KEY)) != NULL) {
        printf("Retrieved (%s) with value (%s)\n", STR_KEY, str);
        free(str);
    }

    e_db_close(db_file);
    e_db_flush();

    return 1;
}

In order to use EDB you must include <Edb.h> in your file to have access to the API. The initial portions of the program are pretty standard, I have a tendency to make typing mistakes so I defined the different keys that I will be using. As long as we have a file name we try to open/create the database.

The database will be opened, or if it doesn't exist, created with the call to e_db_open() which will return NULL if an error was encountered.

Once the database is open we can write our values. This is done through the three calls: e_db_int_set(), e_db_float_set() and e_db_str_set(). You can also set generic data into a db file with e_db_data_set().

Along with normal data, you can store meta-data about the db into the file itself. This data can not be retrieved with the normal get/set methods. These properties are set with e_db_property_set()

Each of the type setting methods takes three parameters:

  • E_DB_File *db

  • char *key

  • value

The value parameter is of the corresponding type to the method, int, float, char * or void * for _int_set, _float_set, _str_set and _data_set respectively.

Once the values are in the db they can be retrieved with the getter methods. Each of these methods takes 3 parameters and returns an int. The return value is 1 on successful retrieval and 0 otherwise.

As with the setter methods, the getter methods parameters are the db, key and a pointer to place to put the value.

Once we're finished with the database we can close it with a call to e_db_close(). The call to e_db_close() does not guarantee that the database has been written to disk, to do this we call e_db_flush() which will write all databases are not being used and writes the contents out to disk.

Example 5.3. Compiling

zero@oberon [edb] -> gcc -o edb edb_ex.c \
	`edb-config --cflags --libs`

If you execute the program you should see the values written out to the screen, and after execution there will be a .db file with the name you specified. You can then take a look at the .db file with edb_gtk_ed and see the values entered.

Recipe: EDB key retrieval

dan 'dj2' sinclair

The EDB API makes it a simple task to retrieve all of the available keys into the database. These keys can then be used to determine the types of the object in the database, or to just retrieve the object if required.

Example 5.4. EDB key retrieval

#include <Edb.h>

int main(int argc, char ** argv) {
    char ** keys;
    int num_keys, i;

    if (argc < 2) 
        return 0;

    keys = e_db_dump_key_list(argv[1], &num_keys);
    for(i = 0; i < num_keys; i++) {
        printf("key: %s\n", keys[i]);
        free(keys[i]); 
    }
    free(keys);
    return 1;
}

Retrieving the keys is done simply through the call to e_db_dump_key_list(). This will return a char ** array of key strings. These strings, and the array itself, must be freed by the application. e_db_dump_key_list() will also return the number of keys in the array in the num_keys parameter.

You'll notice we do not need to open the db in order to call the e_db_dump_key_list(). This function works on the file itself instead of a db object.

Example 5.5. Compiling

zero@oberon [edb] -> gcc -o edb edb_ex.c \
	`edb-config --cflags --libs`

Executing the program should produce a listing of all the keys in the given database. This can be verified by viewing the db through an external tool like, edb_gtk_ed.

Chapter 6. Esmart

Esmart provides a variety of EVAS smart objects that provide significant power to your EVAS and EFL based applications.

Recipe: Esmart Trans Introduction

dan 'dj2' sinclair

Transparency is increasingly becoming a common trait of applications. To this end, the Esmart_Trans object has been created. This object will do all of the hard work to produce a transparent background for your program.

Esmart trans makes the integration of a transparent background into your application very easy. You need to create the trans object, and then make sure you update it as the window is moved or resized.

Example 6.1. Includes and declarations

#include <stdio.h>
#include <Ecore.h>
#include <Ecore_Evas.h>
#include <Esmart/Esmart_Trans_X11.h>

int sig_exit_cb(void *data, int ev_type, void *ev);
void win_del_cb(Ecore_Evas *ee);
void win_resize_cb(Ecore_Evas *ee);
void win_move_cb(Ecore_Evas *ee);

static void _freshen_trans(Ecore_Evas *ee);
void make_gui();

Every application that uses an Esmart_Trans object is going to require the Ecore, Ecore_Evas and the Esmart/Esmart_Trans header files. The next four declarations are callbacks from ecore for events on our window, exit, delete, resize, and move respectively. The last two declarations are convenience functions being used in the example and do not need to be in your program.

Example 6.2. main

int main(int argc, char ** argv) {
    int ret = 0;
        
    if (!ecore_init()) {
        printf("Error initializing ecore\n");
        ret = 1;
        goto ECORE_SHUTDOWN;
    }

    if (!ecore_evas_init()) {
        printf("Error initializing ecore_evas\n");
        ret = 1;
        goto ECORE_SHUTDOWN;
    }
    make_gui();
    ecore_main_loop_begin();
        
    ecore_evas_shutdown();

ECORE_SHUTDOWN:
    ecore_shutdown();
                
    return ret;
}               

The main routine for this example is pretty simple. Ecore and Ecore_Evas are both initialized, with appropriate error checking. We then create the gui and start the main ecore event loop. When ecore exits we shut everything down and return.

Example 6.3. exit and del callbacks

int sig_exit_cb(void *data, int ev_type, void *ev) {
    ecore_main_loop_quit();
    return 1;
}

void win_del_cb(Ecore_Evas *ee) {
    ecore_main_loop_quit();
}

The exit and del callbacks are the generic ecore callbacks. The exit callback isn't strictly necessary, as Ecore will call ecore_main_loop_quit() if no handler is registered, but is included to show how its done.

Example 6.4. _freshen_trans

static void _freshen_trans(Ecore_Evas *ee) {
    int x, y, w, h;
    Evas_Object *o;

    if (!ee) return;

    ecore_evas_geometry_get(ee, &x, &y, &w, &h);
    o = evas_object_name_find(ecore_evas_get(ee), "bg");

    if (!o) {
        fprintf(stderr, "Trans object not found, bad, very bad\n");
        ecore_main_loop_quit();
    }
    esmart_trans_x11_freshen(o, x, y, w, h);
}

The _freshen_trans routine is a helper routine to update the image that the trans is shown. This will be called when we need to update our image to whats currently under the window. The function grabs the current size of the ecore_evas, and then gets the object with the name "bg" (this name is the same as the name we give our trans when we create it). Then, as long as the trans object exists, we tell esmart to freshen the image being displayed.

Example 6.5. resize_cb

void win_resize_cb(Ecore_Evas *ee) {
    int w, h;
    int minw, minh;
    int maxw, maxh;
    Evas_Object *o = NULL;

    if (ee) {
        ecore_evas_geometry_get(ee, NULL, NULL, &w, &h);
        ecore_evas_size_min_get(ee, &minw, &minh);
        ecore_evas_size_max_get(ee, &maxw, &maxh);

        if ((w >= minw) && (h >= minh) && (h <= maxh) && (w <= maxw)) {
            if ((o = evas_object_name_find(ecore_evas_get(ee), "bg")))
                evas_object_resize(o, w, h);
        }
    }
    _freshen_trans(ee);
}

When the window is resized we need to update our evas to the correct size and then update the trans object to display that much of the background. We grab the current size of the window ecore_evas_geometry_get and the min/max size of the window. As long as our currently desired size is within the min/max bounds set for our window, we grab the "bg" (same as title again) object and resize it. Once the resizing is done, we call the _freshen_trans routine to update the image displayed on the bg.

Example 6.6. move_cb

void win_move_cb(Ecore_Evas *ee) {
    _freshen_trans(ee);
}

When the window is moved we need to freshen the image displayed as the transparency.

Example 6.7. Setup ecore/ecore_evas

void make_gui() {
    Evas *evas = NULL;
    Ecore_Evas *ee = NULL;
    Evas_Object *trans = NULL;
    int x, y, w, h;

    ecore_event_handler_add(ECORE_EVENT_SIGNAL_EXIT, sig_exit_cb, NULL);

    ee = ecore_evas_software_x11_new(NULL, 0, 0, 0, 300, 200);
    ecore_evas_title_set(ee, "trans demo");

    ecore_evas_callback_delete_request_set(ee, win_del_cb);
    ecore_evas_callback_resize_set(ee, win_resize_cb);
    ecore_evas_callback_move_set(ee, win_move_cb);

    evas = ecore_evas_get(ee);

The first portion of make_gui is concerned with setting up ecore and ecore_evas. First the exit callback is hooked into ECORE_EVENT_SIGNAL_EXIT, then the Ecore_Evas object is created with the software X11 engine. The window title is set and we hook in the callbacks written above, delete, resize and move. Finally we grab the evas for the created Ecore_Evas.

Example 6.8. Creating Esmart_Trans object

    trans = esmart_trans_x11_new(evas);
    evas_object_move(trans, 0, 0);
    evas_object_layer_set(trans, -5);
    evas_object_name_set(trans, "bg");

    ecore_evas_geometry_get(ee, &x, &y, &w, &h);
    evas_object_resize(trans, w, h);

    evas_object_show(trans);
    ecore_evas_show(ee);

    esmart_trans_x11_freshen(trans, x, y, w, h);
}

Once everything is setup we can create the trans object. The trans is to be created in the evas returned by ecore_evas_get. This initial creation is done by the call to esmart_trans_x11_new. Once we have the object, we move it so it starts at position (0, 0) (the upper left corner), set the layer to -5 and name the object "bg" (as used above). Then we grab the current size of the ecore_evas and use that to resize the trans object to the window size. Once everything is resized we show the trans and show the ecore_evas. As a final step, we freshen the image on the transparency to what is currently under the window so it is up to date.

Example 6.9. Simple makefile

CFLAGS = `ecore-config --cflags` `evas-config --cflags` `esmart-config --cflags`
LIBS = `ecore-config --libs` `evas-config --libs` `esmart-config --libs` \
            -lesmart_trans_x11

all:
    gcc -o trans_example trans_example.c $(CFLAGS) $(LIBS)

In order to compile the above program we need to include the library information for ecore, ecore_evas and esmart. This is done through the -config scripts for each library. These -config scripts know where each of the includes and libraries resides and sets up the appropriate linking and include paths for the compilation.

Recipe: Esmart Container Introduction

dan 'dj2' sinclair

There is usually a desire while designing an apps UI to group common elements together and have their layout depend on one another. To this end the Esmart Container library has been created. It has been designed to handle the hard parts of the layout, and in the cases where it does not do what you need, the layout portions of the container are extensible and changeable.

This recipe will give the basics of using an Esmart container. The final product is a program that will let you see some of the different layout combinations of the default container. The layout will be done by Edje with callbacks to the program to change the container layout, and to tell if the user clicked on a container element.

Example 6.10. Includes and declarations

#include <Ecore.h>
#include <Ecore_Evas.h>
#include <Edje.h>
#include <Esmart/Esmart_Container.h>
#include <getopt.h>

static void make_gui(const char *theme);
static void container_build(int align, int direction, int fill);
static void _set_text(int align, int direction);
static void _setup_edje_callbacks(Evas_Object *o);
static void _right_click_cb(void* data, Evas_Object* o, const char* emmission,
                                                            const char* source);
static void _left_click_cb(void* data, Evas_Object* o, const char* emmission,
                                                            const char* source);
static void _item_selected(void* data, Evas_Object* o, const char* emmission,
                                                            const char* source);

static Ecore_Evas *ee;
static Evas_Object *edje;
static Evas_Object *container;

char *str_list[] = {"item 1", "item 2",
                    "item 3", "item 4",
                    "item 5"};

As with other EFL programs we need to include Ecore, Ecore_Evas, Edje and as this is a container example, the Esmart/Esmart_Container header. Getopt will be used to allow for some command line processing.

Next comes the function prototypes which will be described later when we get to their implementations. Then a few global variables to be used throughout the program. The str_list array is the content to be stored in the container.

Example 6.11. main

int main(int argc, char ** argv) {
    int align = 0;
    int direction = 0;
    int fill = 0;
    int ret = 0;
    int c;
    char *theme = NULL;

    while((c = getopt(argc, argv, "a:d:f:t:")) != -1) {
        switch(c) {
            case 'a':
                align = atoi(optarg);
                break;

            case 'd':
                direction = atoi(optarg);
                break;

            case 'f':
                fill = atoi(optarg);
                break;

            case 't':
                theme = strdup(optarg);
                break;

            default:
                printf("Unknown option string\n");
                break;
        }
    }

    if (theme == NULL) {
        printf("Need a theme defined\n");
        exit(-1);
    }

The beginning of the main function gets the options out of the command line arguments and sets up the default display. As you can see, we require a theme to display. This could be made more intelligent, searching default install directories and the users application directories, but this example takes the easy way out and forces the theme to be a command line option.

Example 6.12. Initialization

    if (!ecore_init()) {
        printf("Can't init ecore, bad\n");
        ret = 1;
        goto EXIT;
    }
    ecore_app_args_set(argc, (const char **)argv);
    
    if (!ecore_evas_init()) { 
        printf("Can't init ecore_evas, bad\n");
        ret = 1;
        goto EXIT_ECORE;
    }
    
    if (!edje_init()) {
        printf("Can't init edje, bad\n");
        ret = 1;
        goto EXIT_ECORE_EVAS;
    }
    edje_frametime_set(1.0 / 60.0);
    
    make_gui(theme);
    container_build(align, direction, fill);
    
    ecore_main_loop_begin();

After receiving the command line arguments, we then proceed to initializing the required libraries, Ecore, Ecore_Evas and Edje. We take the additional step of setting the Edje frame rate.

Once the initialization is complete we create the initial GUI for the app. I have separated the building of the container out into a separate function to make the container code easier to locate in the example.

Once everything is created we call ecore_main_loop_begin and wait for events to occur.

Example 6.13. Shutdown

    edje_shutdown();

EXIT_ECORE_EVAS:
    ecore_evas_shutdown();

EXIT_ECORE:
    ecore_shutdown();

EXIT:
    return ret;
}

The usual end routine, be good programmers and shutdown everything we started.

Example 6.14. Window callbacks

static int sig_exit_cb(void *data, int ev_type, void *ev) {
    ecore_main_loop_quit();
    return 1;
}

static void win_del_cb(Ecore_Evas *ee) {
    ecore_main_loop_quit();
}

static void win_resize_cb(Ecore_Evas *ee) {
    int w, h;
    int minw, minh;
    int maxw, maxh;
    Evas_Object *o = NULL;

    if (ee) {
        ecore_evas_geometry_get(ee, NULL, NULL, &w, &h);
        ecore_evas_size_min_get(ee, &minw, &minh);
        ecore_evas_size_max_get(ee, &maxw, &maxh);

        if ((w >= minw) && (h >= minh) && (h <= maxh) && (w <= maxw)) {
            if ((o = evas_object_name_find(ecore_evas_get(ee), "edje")))
                evas_object_resize(o, w, h);
        }
    }
}

Next we setup some generic callbacks to be used by the UI. This will be the exit, destroy and resize callbacks. Again, the usual EFL style functions. Although the exit callback is not strictly necessary as Ecore itself will call ecore_main_loop_quit() if no handler is registered for this callback.

Example 6.15. make_gui

static void make_gui(const char *theme) {
    Evas *evas = NULL;
    Evas_Object *o = NULL;
    Evas_Coord minw, minh;

    ee = NULL;
    edje = NULL;
    container = NULL;

    ecore_event_handler_add(ECORE_EVENT_SIGNAL_EXIT, sig_exit_cb, NULL);

    ee = ecore_evas_software_x11_new(NULL, 0, 0, 0, 300, 400);
    ecore_evas_title_set(ee, "Container Example");

    ecore_evas_callback_delete_request_set(ee, win_del_cb);
    ecore_evas_callback_resize_set(ee, win_resize_cb);
    evas = ecore_evas_get(ee);
    
    // create the edje
    edje = edje_object_add(evas);
    evas_object_move(edje, 0, 0);
    
    if (edje_object_file_set(edje, theme, "container_ex")) {
        evas_object_name_set(edje, "edje");
        
        edje_object_size_min_get(edje, &minw, &minh);
        ecore_evas_size_min_set(ee, (int)minw, (int)minh);
        evas_object_resize(edje, (int)minw, (int)minh);
        ecore_evas_resize(ee, (int)minw, (int)minh);
        
        edje_object_size_max_get(edje, &minw, &minh);
        ecore_evas_size_max_set(ee, (int)minw, (int)minh);
        evas_object_show(edje);
    
    } else {
        printf("Unable to open (%s) for edje theme\n", theme);
        exit(-1);
    }
    _setup_edje_callbacks(edje);
    ecore_evas_show(ee);
}

The GUI consists of the Ecore_Evas containing the canvas itself, and the Edje that we will be using to control our layout. The make_gui function sets up the callbacks defined above and creates the Ecore_Evas.

Once we have the Evas and the callbacks are defined, we create the Edje object that will define our layout. The edje_object_add call is used to create the object on the Evas, and once thats done, we take the theme passed in by the user and set our Edje to use said theme, the "container_ex" parameter is the name of the group inside the EET that we are to use.

Once the theme file is set to the Edje, we use the values in the theme file to setup the size ranges for the app, and show the Edje. We then setup the callbacks on the Edje and show the Ecore_Evas.

Example 6.16. Edje Callbacks

static void _setup_edje_callbacks(Evas_Object *o) {
    edje_object_signal_callback_add(o, "left_click", 
                        "left_click", _left_click_cb, NULL);
    edje_object_signal_callback_add(o, "right_click", 
                        "right_click", _right_click_cb, NULL);
}

The program will have two main callbacks attached to the Edje, one for the left click signal and one for the right click signal. These will be used to switch the direction/alignment of the container. The second and third parameters of the callbacks need to match the data emitted with the signal from Edje, this will be seen later when we look at the EDC file. The third parameter is the function to call, and the last, any data we wish to be passed into the callback.

Example 6.17. container_build

static void container_build(int align, int direction, int fill_policy) {
    int len = 0;
    int i = 0;
    const char *edjefile = NULL;

    container = esmart_container_new(ecore_evas_get(ee));
    evas_object_name_set(container, "the_container");
    esmart_container_direction_set(container, direction);
    esmart_container_alignment_set(container, align);
    esmart_container_padding_set(container, 1, 1, 1, 1);
    esmart_container_spacing_set(container, 1);
    esmart_container_fill_policy_set(container, fill_policy);

    evas_object_layer_set(container, 0);
    edje_object_part_swallow(edje, "container", container);

The container_build function will create the container and set our data elements in said container. The creation is easy enough with a call to esmart_container_new giving back the Evas_Object that is the container. Once the container is created we can set a name on the container to make reference easier.

Next, we set the direction, which is either (CONTAINER_DIRECTION_VERTICAL or CONTAINER_DIRECTION_HORIZONTAL) [or in this case, an int being passed from the command line as the two directions map to 1 and 0 respectively]. The direction tells the container which way the elements will be drawn.

After the direction we set the alignment of the container. The alignment tells the container where to draw the elements. The possible values are: CONTAINER_ALIGN_CENTER, CONTAINER_ALIGN_LEFT, CONTAINER_ALIGN_RIGHT, CONTAINER_ALIGN_TOP and CONTAINER_ALIGN_BOTTOM. With the default layout, left and right only apply to a vertical container, and top and bottom only apply to a horizontal container, although center applies to both.

If we wanted to use a different layout scheme then the default, we could place a call to esmart_container_layout_plugin_set(container, "name") where the name is the name of the plugin to use. The default setting is the container named "default".

Once the directions and alignment are set, the spacing and padding of the container are specified. The padding specifies the space around the outside of the container taking four numeric parameters: left, right, top and bottom. The spacing parameter specifies the space between elements in the container.

We then continue and set the fill policy of the container. This specifies how the elements are positioned to fill the space in the container. The possible values are: CONTAINER_FILL_POLICY_NONE, CONTAINER_FILL_POLICY_KEEP_ASPECT, CONTAINER_FILL_POLICY_FILL_X, CONTAINER_FILL_POLICY_FILL_Y, CONTAINER_FILL_POLICY_FILL and CONTAINER_FILL_POLICY_HOMOGENOUS.

Once the container is fully specified we set the containers layer, and then swallow the container into the edje and the part named "container".

Example 6.18. Adding Elements to the Container

    len = (sizeof(str_list) / sizeof(str_list[0]));
    for(i = 0; i < len; i++) {
        Evas_Coord w, h;
        Evas_Object *t = edje_object_add(ecore_evas_get(ee));

        edje_object_file_get(edje, &edjefile, NULL);
        if (edje_object_file_set(t, edjefile, "element")) {
            edje_object_size_min_get(t, &w, &h);
            evas_object_resize(t, (int)w, (int)h);
        
            if (edje_object_part_exists(t, "element.value")) {
                edje_object_part_text_set(t, "element.value", str_list[i]);
                evas_object_show(t);
                int *i_ptr = (int *)malloc(sizeof(int));
                *i_ptr = (i + 1);

                edje_object_signal_callback_add(t, "item_selected", 
                                    "item_selected", _item_selected, i_ptr);
    
                esmart_container_element_append(container, t);
            } else {
                printf("Missing element.value part\n");
                evas_object_del(t);
            }
        } else {
            printf("Missing element part\n");
            evas_object_del(t);
        }
    }
    evas_object_show(container);
    _set_text(align, direction);
}

Now that we have a container, we can add some elements to be displayed. Each of the entries in the str_list array defined at the beginning of the program will be added into the container as a text part.

For each element we create a new Edje object on the Evas. We then need to know the name of the theme file used to create our main Edje, so we call edje_object_file_get which will set edje file to said value.

We then try to set the group named "element" onto the newly created element. If this fails we print an error and delete the object.

As long as we have found the group "element" we can attempt to grab the part for our element, "element.value". If this part exists, we set the text value of the part to our current string and show the part.

A callback is created through edje_object_signal_callback_add and attached to the new element. This will be called if the "item_selected" signal is sent from the Edje. The i_ptr value shows how data can be attached to the element, when the user clicks on an element its number will be printed to the console.

Once the element is created we add it to the container (in this case, appending the element).

To finish, the container is show and we do some extra work to display information about the container in the header through the call _show_text.

Example 6.19. _set_text

static void _set_text(int align, int direction) {
    Evas_Object *t = edje_object_add(ecore_evas_get(ee));
    const char *edjefile;

    if (direction == CONTAINER_DIRECTION_VERTICAL)
        edje_object_part_text_set(edje, "header_text_direction", "vertical");
    else
        edje_object_part_text_set(edje, "header_text_d