Edje signals and messages

In this example, we illustrate how Edje signals and Edje messages work.

We place, in the canvas, an Edje object along with a red border image to delimit its geometry. The object's group definition is so that we have four parts:

  • a blue rectangle, aligned to the right
  • a white rectangle, aligned to the left
  • a text part, aligned to the center
  • a clipper rectangle on the blue rectangle

The left rectangle is bound to a color class, so that we can multiply its colors by chosen values on the go:

#define MSG_COLOR 1
#define MSG_TEXT 2

collections {
   group {
      name: "example_group";

      parts {
         part {
            name: "part_right";
            type: RECT;
            clip_to: "part_right_clipper";
            description {
               min: 50 50;
               max: 50 50;
               state: "default" 0.0;
               color: 0 0 255 255; /* blue */
               rel1.relative: 1.0 0.5;
               rel1.offset: -49 0;
               rel2.relative: 1.0 0.5;
            }
         }

         part {
            name: "part_left";
            type: RECT;
            description {
               color_class: "cc";
               min: 50 50;
               max: 50 50;
               state: "default" 0.0;
               rel1.relative: 0.0 0.5;
               rel2.relative: 0.0 0.5;
               rel2.offset: 50 -1;
            }
         }

         part {
            name: "text";
            type: TEXT;
            description {
               min: 150 50;
               max: 150 50;
               fixed: 1 1;
               color: 0 0 0 255;
               state: "default" 0.0;
               rel1.relative: 0.5 0.5;
               rel2.relative: 0.5 0.5;
               text {
                  font:     "Sans";
                  size:     20;
                  min:      1 1;
                  align:    0.5 0.5;
               }
            }
         }

         part {
            name: "part_right_clipper";
            type: RECT;
            repeat_events: 1;

            description {
               min: 50 50;
               max: 50 50;

               state: "default" 0.0;
               rel1.relative: 1.0 0.5;
               rel1.offset: -49 0;
               rel2.relative: 1.0 0.5;
            }

            description {
               state: "hidden" 0.0;
               inherit: "default" 0.0;
               visible: 0;
            }
         }
      }

The define's on the beginning will serve as message identifiers, for our accorded message interface between the code and the this theme file.

Let's move to the code, then. After instantiating the Edje object, we register two signal callbacks on it. The first one uses globbing, making all of the wheel mouse actions over the left rectangle to trigger _mouse_wheel_cb. Note that those kind of signals are generated internally (and automatically) in Edje. The second is a direct signal match, to a (custom) signal we defined in the EDC, ourselves:

   edje_obj = edje_object_add(evas);

   if (!edje_object_file_set(edje_obj, edje_file_path, "example_group"))
     {
        int err = edje_object_load_error_get(edje_obj);
        const char *errmsg = edje_load_error_str(err);
        fprintf(stderr, "Could not load 'example_group' from "
                        "signals-messages.edj: %s\n", errmsg);

        evas_object_del(edje_obj);
        goto error_edj;
     }

   edje_object_signal_callback_add(edje_obj, "mouse,wheel,*", "part_left",
                                   _mouse_wheel_cb, NULL);

   edje_object_signal_callback_add(edje_obj, "mouse,over", "part_right",
                                   _mouse_over_cb, NULL);
/* print signals coming from theme */
static void
_sig_print(const char *emission,
           const char *source)
{
   fprintf(stdout, "Signal %s coming from part %s!\n", emission, source);
}

static void
_mouse_wheel_cb(void        *data __UNUSED__,
                  Evas_Object *obj __UNUSED__,
                  const char  *emission,
                  const char  *source)
{
   _sig_print(emission, source);
}

That second callback is on a signal we emit on the theme, where we just translate Edje "mouse,move" internal events to the custom "mouse,over" one. When that signals reaches the code, we are, besides printing the signals' strings, sending a message back to the theme. We generate random values of color components and send them as an EDJE_MESSAGE_INT_SET message type:

         program { /* custom signal */
            name: "part_right,hovered";
            signal: "mouse,move";
            source: "part_right";
            action: SIGNAL_EMIT "mouse,over" "part_right";
         }
/* mouse over signals */
static void
_mouse_over_cb(void        *data __UNUSED__,
               Evas_Object *obj __UNUSED__,
               const char  *emission,
               const char  *source)
{
   int i;

   _sig_print(emission, source);

   Edje_Message_Int_Set *msg = malloc(sizeof(*msg) + 3 * sizeof(int));
   msg->count = 4;
   for (i = 0; i < 4; i++)
     msg->val[i] = rand() % 256;

   edje_object_message_send(edje_obj, EDJE_MESSAGE_INT_SET, MSG_COLOR, msg);

   free(msg);
}

In our theme we'll be changing the "cc" color class' values with those integer values of the message, so that moving the mouse over the right rectangle will change the left one's colors:

        public message(Msg_Type:type, id, ...) {
           if ((type == MSG_INT_SET) && (id == MSG_COLOR)) {
               new r, g, b, a;

               r = getarg(2);
               g = getarg(3);
               b = getarg(4);
               a = getarg(5);

               set_color_class("cc", r, g, b, a);
           }
        }

Now we're also sending messages from the Edje object, besides signals. We do so when one clicks with the left button over the left rectangle. With that, we change the text part's text, cycling between 3 pre-set strings declared in the EDC. With each new text string attribution, we send a string message to our code, with the current string as argument:

      programs {
         program {
            name: "bootstrap";
            signal: "load";
            source: "";
            script {
               set_str(global_str0, "String one");
               set_str(global_str1, "String two");
               set_str(global_str2, "String three");
               set_int(str_idx, 0);
               set_text_string();
            }
         }
         program { /* change text part's string value */
            name: "text,change";
            signal: "mouse,clicked,1";
            source: "part_left";
            script {
               new idx;
               idx = get_int(str_idx);
               idx = idx + 1;

               if (idx > 2)
                  set_int(str_idx, 0);
               else
                  set_int(str_idx, idx);

               set_text_string();
            }
         }
        public set_text_string() {
           new tmp[1024];
           new idx;
           idx = get_int(str_idx);

           if (idx == 0)
              get_str(global_str0, tmp, 1024);
           else if (idx == 1)
              get_str(global_str1, tmp, 1024);
           else if (idx == 2)
              get_str(global_str2, tmp, 1024);
              else return;

           set_text(PART:"text", tmp);
           send_message(MSG_STRING, MSG_TEXT, tmp);
        }

To get the message in code, we have to register a message handler, as follows:

   edje_object_message_handler_set(edje_obj, _message_handle, NULL);
/* print out received message string */
static void
_message_handle(void             *data __UNUSED__,
                Evas_Object      *obj __UNUSED__,
                Edje_Message_Type type,
                int               id,
                void             *msg)
{
   Edje_Message_String *m;

   if (type != EDJE_MESSAGE_STRING) return;
   if (id != MSG_TEXT) return;

   m = msg;

   fprintf(stdout, "String message received: %s\n", m->str);
}

To interact with the last missing feature -- emitting signals from code -- there's a command line interface to exercise it. A help string can be asked for with the 'h' key:

static const char *commands = \
  "commands are:\n"
  "\tt - toggle right rectangle's visibility\n"
  "\th - print help\n";

The 't' command will send either "part_right,show" or "part_right,hide" signals to the Edje object (those being the emission part of the signal), which was set to react on them as the names indicate. We'll set the right rectangle's visibility on/off, respectively, for those two signals:

         program { /* hide right rectangle */
            name: "part_right,hide";
            signal: "part_right,hide";
            source: "";
            action: STATE_SET "hidden" 0.0;
            target: "part_right_clipper";
         }

         program {
            name: "part_right,show";
            signal: "part_right,show";
            source: "";
            action: STATE_SET "default" 0.0;
            target: "part_right_clipper";
         }
   if (strcmp(ev->keyname, "t") == 0) /* toggle right rectangle's visibility */
     {
        char buf[1024];

        right_rect_show = !right_rect_show;
        snprintf(buf, sizeof(buf), "part_right,%s",
                 right_rect_show ? "show" : "hide");

        printf("emitting %s\n", buf);

        edje_object_signal_emit(edje_obj, buf, "");

        return;
     }

The example's window should look like this picture:

edje-signals-messages-example.png

The full example follows, along with its EDC file.

#define MSG_COLOR 1
#define MSG_TEXT 2

collections {
   group {
      name: "example_group";

      parts {
         part {
            name: "part_right";
            type: RECT;
            clip_to: "part_right_clipper";
            description {
               min: 50 50;
               max: 50 50;
               state: "default" 0.0;
               color: 0 0 255 255; /* blue */
               rel1.relative: 1.0 0.5;
               rel1.offset: -49 0;
               rel2.relative: 1.0 0.5;
            }
         }

         part {
            name: "part_left";
            type: RECT;
            description {
               color_class: "cc";
               min: 50 50;
               max: 50 50;
               state: "default" 0.0;
               rel1.relative: 0.0 0.5;
               rel2.relative: 0.0 0.5;
               rel2.offset: 50 -1;
            }
         }

         part {
            name: "text";
            type: TEXT;
            description {
               min: 150 50;
               max: 150 50;
               fixed: 1 1;
               color: 0 0 0 255;
               state: "default" 0.0;
               rel1.relative: 0.5 0.5;
               rel2.relative: 0.5 0.5;
               text {
                  font:     "Sans";
                  size:     20;
                  min:      1 1;
                  align:    0.5 0.5;
               }
            }
         }

         part {
            name: "part_right_clipper";
            type: RECT;
            repeat_events: 1;

            description {
               min: 50 50;
               max: 50 50;

               state: "default" 0.0;
               rel1.relative: 1.0 0.5;
               rel1.offset: -49 0;
               rel2.relative: 1.0 0.5;
            }

            description {
               state: "hidden" 0.0;
               inherit: "default" 0.0;
               visible: 0;
            }
         }
      }

      script {
        public global_str0;
        public global_str1;
        public global_str2;
        public str_idx;

        public set_text_string() {
           new tmp[1024];
           new idx;
           idx = get_int(str_idx);

           if (idx == 0)
              get_str(global_str0, tmp, 1024);
           else if (idx == 1)
              get_str(global_str1, tmp, 1024);
           else if (idx == 2)
              get_str(global_str2, tmp, 1024);
              else return;

           set_text(PART:"text", tmp);
           send_message(MSG_STRING, MSG_TEXT, tmp);
        }

        public message(Msg_Type:type, id, ...) {
           if ((type == MSG_INT_SET) && (id == MSG_COLOR)) {
               new r, g, b, a;

               r = getarg(2);
               g = getarg(3);
               b = getarg(4);
               a = getarg(5);

               set_color_class("cc", r, g, b, a);
           }
        }
      }

      programs {
         program {
            name: "bootstrap";
            signal: "load";
            source: "";
            script {
               set_str(global_str0, "String one");
               set_str(global_str1, "String two");
               set_str(global_str2, "String three");
               set_int(str_idx, 0);
               set_text_string();
            }
         }

         program { /* custom signal */
            name: "part_right,hovered";
            signal: "mouse,move";
            source: "part_right";
            action: SIGNAL_EMIT "mouse,over" "part_right";
         }

         program { /* hide right rectangle */
            name: "part_right,hide";
            signal: "part_right,hide";
            source: "";
            action: STATE_SET "hidden" 0.0;
            target: "part_right_clipper";
         }

         program {
            name: "part_right,show";
            signal: "part_right,show";
            source: "";
            action: STATE_SET "default" 0.0;
            target: "part_right_clipper";
         }

         program { /* change text part's string value */
            name: "text,change";
            signal: "mouse,clicked,1";
            source: "part_left";
            script {
               new idx;
               idx = get_int(str_idx);
               idx = idx + 1;

               if (idx > 2)
                  set_int(str_idx, 0);
               else
                  set_int(str_idx, idx);

               set_text_string();
            }
         }
      }
   }
}
#ifdef HAVE_CONFIG_H
#include "config.h"
#else
#define PACKAGE_EXAMPLES_DIR "."
#define __UNUSED__
#endif

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

#define WIDTH     (300)
#define HEIGHT    (300)

#define MSG_COLOR 1
#define MSG_TEXT  2

static const char *border_img_path = PACKAGE_EXAMPLES_DIR "/red.png";
static const char *edje_file_path = \
  PACKAGE_EXAMPLES_DIR "/signals-messages.edj";

static Ecore_Evas *ee;
static Evas_Object *edje_obj;
static Eina_Bool right_rect_show = EINA_TRUE;

static const char *commands = \
  "commands are:\n"
  "\tt - toggle right rectangle's visibility\n"
  "\th - print help\n";

static void
_on_keydown(void        *data __UNUSED__,
            Evas        *evas __UNUSED__,
            Evas_Object *o __UNUSED__,
            void        *einfo)
{
   Evas_Event_Key_Down *ev = einfo;

   if (strcmp(ev->keyname, "h") == 0) /* print help */
     {
        fprintf(stdout, commands);
        return;
     }

   if (strcmp(ev->keyname, "t") == 0) /* toggle right rectangle's visibility */
     {
        char buf[1024];

        right_rect_show = !right_rect_show;
        snprintf(buf, sizeof(buf), "part_right,%s",
                 right_rect_show ? "show" : "hide");

        printf("emitting %s\n", buf);

        edje_object_signal_emit(edje_obj, buf, "");

        return;
     }
}

static void
_on_delete(Ecore_Evas *ee __UNUSED__)
{
   ecore_main_loop_quit();
}

/* print signals coming from theme */
static void
_sig_print(const char *emission,
           const char *source)
{
   fprintf(stdout, "Signal %s coming from part %s!\n", emission, source);
}

static void
_mouse_wheel_cb(void        *data __UNUSED__,
                  Evas_Object *obj __UNUSED__,
                  const char  *emission,
                  const char  *source)
{
   _sig_print(emission, source);
}

/* mouse over signals */
static void
_mouse_over_cb(void        *data __UNUSED__,
               Evas_Object *obj __UNUSED__,
               const char  *emission,
               const char  *source)
{
   int i;

   _sig_print(emission, source);

   Edje_Message_Int_Set *msg = malloc(sizeof(*msg) + 3 * sizeof(int));
   msg->count = 4;
   for (i = 0; i < 4; i++)
     msg->val[i] = rand() % 256;

   edje_object_message_send(edje_obj, EDJE_MESSAGE_INT_SET, MSG_COLOR, msg);

   free(msg);
}

/* print out received message string */
static void
_message_handle(void             *data __UNUSED__,
                Evas_Object      *obj __UNUSED__,
                Edje_Message_Type type,
                int               id,
                void             *msg)
{
   Edje_Message_String *m;

   if (type != EDJE_MESSAGE_STRING) return;
   if (id != MSG_TEXT) return;

   m = msg;

   fprintf(stdout, "String message received: %s\n", m->str);
}

int
main(void)
{
   Evas_Object *border, *bg;
   Evas *evas;

   srand(time(NULL));

   if (!ecore_evas_init())
     return EXIT_FAILURE;

   if (!edje_init())
     return EXIT_FAILURE;

   /* this will give you a window with an Evas canvas under the first
    * engine available */
   ee = ecore_evas_new(NULL, 0, 0, WIDTH, HEIGHT, NULL);
   if (!ee)
     goto error;

   ecore_evas_callback_delete_request_set(ee, _on_delete);
   ecore_evas_title_set(ee, "Edje Basics Example");
   ecore_evas_show(ee);

   evas = ecore_evas_get(ee);

   bg = evas_object_rectangle_add(evas);
   evas_object_color_set(bg, 255, 255, 255, 255); /* white bg */
   evas_object_move(bg, 0, 0); /* at canvas' origin */
   evas_object_resize(bg, WIDTH, HEIGHT); /* covers full canvas */
   evas_object_show(bg);
   ecore_evas_object_associate(ee, bg, ECORE_EVAS_OBJECT_ASSOCIATE_BASE);

   evas_object_focus_set(bg, EINA_TRUE);
   evas_object_event_callback_add(
     bg, EVAS_CALLBACK_KEY_DOWN, _on_keydown, NULL);

   edje_obj = edje_object_add(evas);

   if (!edje_object_file_set(edje_obj, edje_file_path, "example_group"))
     {
        int err = edje_object_load_error_get(edje_obj);
        const char *errmsg = edje_load_error_str(err);
        fprintf(stderr, "Could not load 'example_group' from "
                        "signals-messages.edj: %s\n", errmsg);

        evas_object_del(edje_obj);
        goto error_edj;
     }

   edje_object_signal_callback_add(edje_obj, "mouse,wheel,*", "part_left",
                                   _mouse_wheel_cb, NULL);

   edje_object_signal_callback_add(edje_obj, "mouse,over", "part_right",
                                   _mouse_over_cb, NULL);

   edje_object_message_handler_set(edje_obj, _message_handle, NULL);

   evas_object_move(edje_obj, 20, 20);
   evas_object_resize(edje_obj, WIDTH - 40, HEIGHT - 40);
   evas_object_show(edje_obj);

   /* this is a border around the Edje object above, here just to
    * emphasize its geometry */
   border = evas_object_image_filled_add(evas);
   evas_object_image_file_set(border, border_img_path, NULL);
   evas_object_image_border_set(border, 2, 2, 2, 2);
   evas_object_image_border_center_fill_set(border, EVAS_BORDER_FILL_NONE);

   evas_object_resize(border, WIDTH - 40 + 4, HEIGHT - 40 + 4);
   evas_object_move(border, 20 - 2, 20 - 2);
   evas_object_repeat_events_set(border, EINA_TRUE);
   evas_object_show(border);

   fprintf(stdout, commands);
   ecore_main_loop_begin();

   ecore_evas_free(ee);
   ecore_evas_shutdown();
   edje_shutdown();
   return 0;

error:
   fprintf(stderr, "You got to have at least one Evas engine built"
                   " and linked up to ecore-evas for this example to run"
                   " properly.\n");
   ecore_evas_shutdown();
   return -1;

error_edj:
   fprintf(stderr, "Failed to load signals-messages.edj!\n");

   ecore_evas_shutdown();
   return -2;
}