Edje Messages vs Signals

We saw in the previous Edje post how to send Signals down the Edje elements. We’re now going to see another way of communicating between the application and the Edje theme : Messages.

1. Difference between Messages and Signals

That was my first question when I discovered Messages : “what is this stuff for, since we have signals?”

The answer is :
Signals allow you to send some discrete (punctual) information. It fits really well in an “action” type communication “do this”, “hey this event happened”, …

Now what if you want, for example, to send data (which could be anything, like a bunch of strings, …), to the theme, or the other way? This doesn’t really fit in the signal+source mold. This is where messages come in handy.

For a concrete example, you could see the existing E “alarm” widget. (I will try to find the URL when I have time)

2. Structure of a message

Three things define a message :
- its Type
- its ID
- its data

The first one is obvious and tells the type of data contained. In C the existing types are defined by EDJE_MESSAGE_* (see Edje.h), and in Embryo as Edje_Type:MSG_*
The second one is an integer that allows you to know what is the function of the message. Use whatever you want, put the defined values in some header shared between your code and your edc files.
The last one is the data.

Now, about the data, you have a few defined types :
Edje_Message_String is just one string
Edje_Message_Int is just one int (ok, that was easy)
Edje_Message_Float oh well you guessed it…
Edje_Message_String_Set more interesting, a variable number of strings (in an array), the number of strings is a count field in the struct.
Edje_Message_Float_Set and Edje_Message_Int_Set, same as the previous one but with floats and strings
Edje_Message_String_Int, Edje_Message_String_Float, a string with an integer (or a float), could be used as a key => value pair for example
Edje_Message_String_Int_Set, Edje_Message_String_Int_Set, a string and an array of integers/floats (with the additional count field).

Note that the set is defined in the struct as an int[1], float[1], or char *[1]. This means you have to allocate your structure to a size bigger depending on the number of elements.


/* allocate a struct for 4 integers */
Edje_Message_String_Int_Set *msg = malloc(sizeof(Edje_Message_String_Int_Set + (4 - 1) * sizeof(int));
/* allocate a struct for 10 string pointers */
Edje_Message_String_Set *msg2 = malloc(sizeof(Edje_Message_String_Set + (10 - 1) * sizeof(char *));

3. Sending a message from Edje to the application

3.1 Binding a message callback

#define MSG_ID_GET_TIME 0
#define MSG_ID_GET_FLOAT AND STR 1

/* your callback */
void message_cb(void *data, Evas_Object *obj, Edje_Message_Type type, int id, void *msg)
{
    if (id == MSG_ID_GET_TIME && type == EDJE_MESSAGE_INT_SET) {
        /* check the right number of integers */
        if (msg->count != 3)
            return;
        int seconds, minutes, hours;
        hours = msg->val[0];
        minutes = msg->val[1];
        seconds = msg->val[2];
        /* do whatever processing you want with your values */
        ...
    } else if (id == MSG_ID_GET_FLOAT_AND_STR && type == EDJE_MESSAGE_STRING_FLOAT) {
        float f;
        char *str;
        f = msg->val;
        str = msg->str;
        /* processing again */
        ...
    }
}

int main()
{
    /* initialization and all */
    ...
    edje_object_message_handler_set(my_edje_object, &message_cb, NULL);
    ...
    /* more stuff after that, you could set the handler at anytime you want anyway */
}

3.2 Sending the message using Embryo

This probably has to be defined in the “top” group loaded as an Evas, I don’t know what would happen otherwise.

group {
    programs {
        program {
            name: "my_program";
            signal: "whatever";
            source: "same";
            script {
                send_message(MSG_INT_SET, MSG_ID_GET_TIME, 5, 10, 3);
                send_message(MSG_STRING_FLOAT, MSG_ID_GET_FLOAT_AND_STR, 5.2, "hello world");
            }
        }
    }
}

4. Sending a message from the application to Edje

Now, if you would want to do it the other way

4.1 Create an embryo callback in the skin

group {
    name: "mygroup";
    script {
        /* this function is automatically bound
          * you may have noticed we don't pass a structure but a variable
          * number of arguments */
        public message(Message_Type:type, int id, ...) {
            if (type == MSG_INT_SET && id == MSG_ID_GET_TIME) {
                /* look in the embryo doc if you need to count the arguments
                  * the "count" variable is not provided in the arguments */
                int sec = getarg(2);
                int min = getarg(3);
                int hour = getarg(4);
                /* do whatever you want in embryo with it */
            } else if (type == MSG_STRING_FLOAT && id == MSG_ID_GET_FLOAT_AND_STR) {
                float f = getfarg(2); /* get a float */
                new str[128];
                getsarg(3, str, 128);
                /* do whatever you want in embryo with it */
            }
        }
    }
}

4.2 Sending the message in the code

/* allocate a struct for 3 integers */
Edje_Message_Int_Set *msg = malloc(sizeof(Edje_Message_Int_Set + (3 - 1) * sizeof(int));
msg->val[0] = 10;
msg->val[1] = 42;
msg->val[2] = 31;
edje_object_send_message(my_edje, EDJE_MESSAGE_INT_SET, MSG_ID_GET_TIME, msg);
/* allocate a struct for string & float */
Edje_Message_String_Float *msg2 = malloc(sizeof(Edje_Message_String_Float);
msg2->str = "I can haz cheezburger";
msg2->val = 0.7777;
edje_object_message_send(my_edje, EDJE_MESSAGE_STRING_FLOAT, MSG_ID_GET_FLOAT_AND_STR, msg2);

Edje Signals, Callbacks and propagation.

As you may already know, interaction with an edje file is down mostly with signals.

You can set up a group with a part and some signal to detect when the part has been clicked

group {
  name: "my_group"
  parts {
    part {
      name: "button";
      type: RECT;
      description {
        state: "default" 0.0;
        color: 255 0 0 255;
        min: 50 50;
      }
    }
  }
  programs {
    program {
      signal: "mouse,up,*";
      source: "button";
      action: SIGNAL_EMIT "button_clicked" "";
    }
  }
}

When the red rectangle is clicked, it will emit a signal {“button_clicked”, “”}

Imagine you want to do something in your program when the button is clicked. You have to add a callback for this :

{
  Evas_Object *evas_obj = edje_object_add(evas);
  edje_object_file_set(evas_obj, "my_theme.edj", "my_group");
  /* needed evas resize/move/show */
  evas_object_resize(evas_obj, 800, 480);
  evas_object_move(evas_obj, 0, 0);
  evas_object_show(evas_obj);

  /* add a callback */
  edje_object_signal_callback_add(evas_obj, "button_clicked", "", &my_callback, NULL);
  /*  simulate a mouse signal on the button to see if it works */
  edje_object_signal_emit(evas_obj, "mouse,up,acme", "button");
}
...
void my_callback(void *data, Evas_Object *o, const char *emission, const char *source)
{
  if (strcmp(emission, "button_clicked") == 0 && strcmp(source, "") == 0) {
    /* do something */
  }
}

As you may notice, there are 4 arguments to the callback :
- data, corresponds to the last field of callback_add. You can pass whatever you want to it, but be sure to have it allocated on the heap. You don’t know when the callback will be launched, so if you specify a pointer to a variable that was allocated in the function stack, you’re going to have some problems.
- evas_object will be the object that emitted a signal (will correspond to evas_obj here)
- emission and source correspond to the signal and the source. You can use the same callback for many signals and dispatch however you want after that.

This is basic stuff you will see in any tutorial, and may lead you to get a bad habit : allocating an Evas_Object for each group of your theme and integrating those in the code.
The ideal goal in an edje application is to have as little code as possible, and move the logic to the edje. That way, you can make a completely different interface (including the way it works, not just graphics) for the same program.
Instead of loading each group in an Evas_Object and use evas to show, hide, move, resize all the elements, juste do one big group with subparts.

group {
  name: "main";
  parts {
    part {
      name: "instance_of_my_group1";
      type: GROUP;
      source: "my_group";
      description {
        state: "default" 0.0;
      }
    }
    part {
      name: "instance_of_my_group2";
      type: GROUP;
      source: "my_group";
      description {
        state: "default" 0.0;
      }
    }
  }
}

What this does is create two instances of the same group, loaded into the “main” group. You only need to instantiate main (edje_object_add and edje_object_file_set) to create those two buttons.
Your next question will be : but if I don’t have an Evas_Object of the group, how do I emit signals to them, how do I add callbacks?

Signal. Propagation.

{
  Evas_Object *main_obj = edje_object_add(evas);
  edje_object_file_set(main_obj, "my_theme.edj", "main");
  /* needed evas resize/move/show */
  evas_object_resize(evas_obj, 800, 480);
  evas_object_move(evas_obj, 0, 0);
  evas_object_show(evas_obj);

  edje_object_signal_callback_add(main_obj, ""button_clicked", "instance_of_my_group1:", &my_callback, NULL);
  /*  simulate a mouse signal on the button to see if it works */
  edje_object_signal_emit(evas_obj, "instance_of_my_group1:mouse,up,acme", "button");
}
...
void my_callback(void *data, Evas_Object *o, const char *emission,  const char *source)
{
  if (strcmp(emission, "button_clicked") == 0 &&  strcmp(source, "instance_of_my_group1:") == 0) {
    /* do something */
  }
}

Understand this important thing :

If you want to emit a signal to a subgroup of an edje object, you have to prefix the signal with the subgroup instance name.
{ “mysubgroup:signal”, “source” }

If you want to add a callback to a signal emitted by a subgroup of an edje object, you have to prefix the source with the subgroup instance name.
{ “signal”, “mysubgroup:source” }

Edje will take care to dispatch those signals automagically. This works with an infinite number of group levels. You could emit {“group:subgroup:subsubgroup:signal”, “source”}.

Now, if you have elements put in a table, or a box, you cannot (at least for now, but it would be a good addition to edje) send signals or get signals from the sub-elements. No “mybox[4]:signal” … for now.

Edit (18/05/2010) :

When you send a signal from the object (if the item was inserted in Edje in the box.items{}), I think the object name is skipped, as if the table itself had sent the signal.

I’ve submitted a patch that would allow someone to send signals to box elements by using this syntax :
{“boxpartname:idx:signal”, “source”} where idx is the index of the element you want to send the signal too, let’s hope it gets accepted.

Edit (20/05/2010) :

Add the evas_object_resize/move/show when initializing the edje object.

Follow

Get every new post delivered to your Inbox.