Navigation
Personal tools
 

EWL Introduction II

This website is deprecated!
Please refer and move articles to: http://trac.enlightenment.org/e/wiki/

In this second introductory article we're going to modify the image viewing application we created in EWL Introduction to show a grid of image icons. We'll allow the user to either specify an image or directory on startup and allow them to drag and drop images or directories into the running application.

The final application will look similar to:

image:Thumber.png

Code Explanation

I'm not going to explain every line of code in this example. Instead, I'm just going to look at the code that differs significantly from the code explained in EWL Introduction.

   #include <Ecore_File.h>

Since I don't want to re-invent the wheel here I'm going to be using Ecore_File to handle checking for file existence and listing directories. There are quite a few routines for dealing with files in Ecore_File, if you need to do a lot of file work it might be worth checking out.

    static void
    handle_image(const char *path)
    {
        Ewl_Widget *free, *o;
        char *file;

        free = ewl_widget_name_find("image_box");

        file = strrchr(path, '/');
        if (file) file++;

        o = ewl_icon_new();
        ewl_icon_image_set(EWL_ICON(o), path, NULL);
        ewl_icon_label_set(EWL_ICON(o), file);
        ewl_icon_constrain_set(EWL_ICON(o), 128);
        ewl_container_child_append(EWL_CONTAINER(free), o);
        ewl_widget_show(o);
    }

The handle_image() function is what we use to actually display the image. I retrieves our image container using ewl_widget_name_find(). We're using an Ewl_Image widget to do the actual displaying. Ewl_Image will thumbnail the given image down to the desired size. (This requires Epsilon to be installed. I'm not sure what it'll do without Epsilon.)

The image is set by calling ewl_icon_image_set(). ewl_icon_image_set() takes parameters similar to the ewl_image_file_set() call we saw in EWL Introduction. The call to ewl_icon_label_set() sets the file name as the label to be displayed below our image. In order to make the image a good size we use ewl_icon_constrain_set() to set the preferred size of our icons.

    static void
    handle_uri(const char *uri)
    {
        const char *ptr = NULL;

        /* see if this is a file pointer */
        if (!strncmp(uri, "file://", 7))
                ptr = uri + 7;
        else
                ptr = uri;

        /* do nothing if the file doesn't exists. not sure if this can
         * happen or not */
        if (!ecore_file_exists(ptr))
                return;

        if (ecore_file_is_dir(ptr))
        {
                Ecore_List *files;
                char *file, path[PATH_MAX];

                files = ecore_file_ls(ptr);
                while ((file = ecore_list_first_remove(files)))
                {
                        snprintf(path, sizeof(path), "%s/%s", ptr, file);
                        handle_image(path);

                        free(file);
                }
                ecore_list_destroy(files);
        }
        else
                handle_image(ptr);
    }

handle_uri() does the work of figuring out if the given item is a file or directory. If file, it just calls handle_image() to display the image. If it's a directory it uses ecore_file_ls() to get the listing of files in the directory and displays each one with handle_image(). Each file name is allocated by ecore_file_ls, so to avoid a memory leak it must free the filename as each one is removed from the list.

    static void
    cb_dnd_data(Ewl_Widget *w, void *ev, void *data)
    {
        Ewl_Event_Dnd_Data_Received *e;
        char ** files;
        int i;

        e = ev;
        files = e->data;

        for (i = 0; i < e->len; i++)
                handle_uri(files[i]);
    }

cb_dnd_data() is our callback function when there has been a DND drop on our widget. Since we're only listening for the text/uri-list MIME type we know what type of data is in Ewl_Event_Dnd_Data_Received and can just work with it. If you're listening to multiple MIME types you'll probably want to check e->type for each MIME type so you act as needed.

The text/uri-list will put a char * array into e->data. We loop over each file in the given array and call handle_uri() to attempt to display the given item.

    static void
    cb_clear(Ewl_Widget *w, void *ev, void *data)
    {
        Ewl_Widget *free;

        free = ewl_widget_name_find("image_box");
        ewl_container_reset(EWL_CONTAINER(free));
    }

cb_clear() will be hooked into the button displayed on the bottom of the application. When it's clicked we clear out all the currently displayed icons. The clearing is as simple as resetting the container by calling ewl_container_reset().

    int
    main(int argc, char ** argv)
    {
        Ewl_Widget *win, *box, *s, *o;
        const char *drop_types[] = {"text/uri-list", NULL};
        int ret = 1;

        if (!ecore_init())
        {
                fprintf(stderr, "Unable to initialize Ecore.\n");
                goto SHUTDOWN;
        }

        if (!ecore_file_init())
        {
                fprintf(stderr, "Unable to initialize Ecore_File.\n");
                goto ECORE_SHUTDOWN;
        }

        if (!ewl_init(&argc, argv))
        {
                fprintf(stderr, "Unable to initialize EWL.\n");
                goto ECORE_FILE_SHUTDOWN;
        }

Since we're using Ecore_File in this application we need to make sure everything is initialized. The two pieces we want to initialize are Ecore and Ecore_File. I don't like messy return and shutdown code everywhere so I'm using a copy of goto calls to handle my cleanup for me.

You'll also notice the array being created: const char *drop_types[] = {"text/uri-list", NULL};. This will be the list of DND MIME types that our Ewl_Freebox widget will accept. We could put any valid MIME type we want into the list.

       box = ewl_vbox_new();
       ewl_container_child_append(EWL_CONTAINER(win), box);
       ewl_widget_show(box);

Instead of just packing our scrollpane into the window like we did last time I'm creating a box to put everything in this time. There are two types of boxes in EWL; vertical and horizontal. As a convenience we've created two wrapper calls; ewl_vbox_new() and ewl_hbox_new() to create these two types of boxes, respectively.

       o = ewl_freebox_new();
       ewl_container_child_append(EWL_CONTAINER(s), o);
       ewl_dnd_accepted_types_set(o, drop_types);
       ewl_callback_append(o, EWL_CALLBACK_DND_DATA_RECEIVED, cb_dnd_data, NULL);
       ewl_widget_name_set(o, "image_box");
       ewl_widget_show(o);

We're going to use an Ewl_Freebox to hold our images. The freebox does layout in a grid like fashion. It can also handle things like sorting the items based on an arbitrary callback that can be assigned. Since we want this widget to be able to handle DND events we need to let EWL know what DND MIME types it's interested in. This is done by calling ewl_dnd_accepted_types_set() and providing the array of types.

With the accepted types setup we can then hookup a callback to listen for EWL_CALLBACK_DND_DATA_RECEIVED events. This will be triggered when the user has done an actual DND drop on our widget and the drop data has become available. You can also listen for EWL_CALLBACK_DND_ENTER, EWL_CALLBACK_DND_LEAVE, EWL_CALLBACK_DND_DROP' and EWL_CALLBACK_DND_POSITION.

       s = ewl_hbox_new();
       ewl_object_fill_policy_set(EWL_OBJECT(s), EWL_FLAG_FILL_HFILL |
                                               EWL_FLAG_FILL_VSHRINK);
       ewl_container_child_append(EWL_CONTAINER(box), s);
       ewl_widget_show(s);

Since I wanted to put a control button on the bottom of the application I created a horizontal box to pack it into. The Ewl_Box code will provide each widget as much space as it desires when it's displaying. In this case we don't want that. We want our scrollpane created earlier to have as much space as it needs and the control box to be as small as possible vertically. To achieve this we use ewl_object_fill_policy_set() to tell the box to fill horizontally (EWL_FLAG_FILL_HFILL) and to shrink vertically (EWL_FLAG_FILL_VSHIRNK). This will make sure the box fills the window horizontal but takes as little space as possible in the vertical.

       o = ewl_button_new();
       ewl_button_label_set(EWL_BUTTON(o), "clear");
       ewl_callback_append(o, EWL_CALLBACK_CLICKED, cb_clear, NULL);
       ewl_object_fill_policy_set(EWL_OBJECT(o), EWL_FLAG_FILL_SHRINK);
       ewl_container_child_append(EWL_CONTAINER(s), o);
       ewl_widget_show(o);

We're going to pack the clear button into our new box. If you want you can also use stock buttons in EWL. These buttons will have a pre-defined label and image attached to them. The list of stock types can be seen in ewl_enums.h just search for STOCK. In this case we just want to display the word clear on our button. This is done by calling ewl_button_label_set().

In order to get notified when the user clicks on the button we need to hookup an EWL_CALLBACK_CLICKED callback. This will execute the cb_clear() function we defined previously.

I'm also changing the fill policy of the button. By default a button will expand to fill its available space. I'm not a big fan of the way this looks and typically modify my buttons to have a EWL_FLAG_FILL_SHIRNK fill policy. EWL_FLAG_FILL_SHRINK is just a quick way to saying: EWL_FLAG_FILL_HSHRINK | EWL_FLAG_FILL_VSHRINK.

        if (argc > 1)
                handle_uri(argv[1]);

        ewl_main();
        ret = 0;

    ECORE_FILE_SHUTDOWN:
        ecore_file_shutdown();
    ECORE_SHUTDOWN:
        ecore_shutdown();
    SHUTDOWN:
        return ret;
    }

Finally, we display anything specified on the command line and start the main loop. We need to make sure we call ecore_file_shutdown() and ecore_shutdown() to clean everything up once we're finished with them.

You can compile the above program with:

   dj2@oni:~/t$ gcc -o free free.c `pkg-config --cflags --libs ewl`

With that, hopefully, you've seen how simple it can be to create slightly more complex applications including things like DND drop support and slightly more user interaction. Go on and learn more in the EWL Introduction III.

Complete Code Listing

    #include <Ewl.h>
    #include <Ecore_File.h> 
    #include <stdio.h>
    #include <string.h> 
                
    #define PATH_MAX 1024
                        
    static void         
    handle_image(const char *path)
    {                   
        Ewl_Widget *free, *o;
        char *file;
        
        free = ewl_widget_name_find("image_box");
                
        file = strrchr(path, '/');
        if (file) file++;
    
        o = ewl_icon_new();
        ewl_icon_image_set(EWL_ICON(o), path, NULL);
        ewl_icon_label_set(EWL_ICON(o), file);
        ewl_icon_constrain_set(EWL_ICON(o), 128);
        ewl_container_child_append(EWL_CONTAINER(free), o);
        ewl_widget_show(o);
    }
    
    static void
    handle_uri(const char *uri)
    {   
        const char *ptr = NULL;
        
        /* see if this is a file pointer */
        if (!strncmp(uri, "file://", 7))
                ptr = uri + 7;
        else
                ptr = uri;
    
        /* do nothing if the file doesn't exists. not sure if this can
         * happen or not */
        if (!ecore_file_exists(ptr))
                return;

        if (ecore_file_is_dir(ptr))
        {
                Ecore_List *files;
                char *file, path[PATH_MAX];
                
                files = ecore_file_ls(ptr);
                while ((file = ecore_list_first_remove(files)))
                {
                        snprintf(path, sizeof(path), "%s/%s", ptr, file);
                        handle_image(path);
        
                        free(file);
                }
                ecore_list_destroy(files);
        }
        else
                handle_image(ptr);
    }
    
    static void
    cb_window_delete(Ewl_Widget *w, void *ev, void *data)
    {   
        ewl_main_quit();
    }   
        
    static void 
    cb_key_down(Ewl_Widget *w, void *ev, void *data)
    {
        Ewl_Event_Key_Down *e;

        e = ev;

        if ((!strcmp(e->base.keyname, "Escape"))
                        || (!strcmp(e->base.keyname, "q")))
                ewl_main_quit();
    }

    static void
    cb_dnd_data(Ewl_Widget *w, void *ev, void *data)
    {
        Ewl_Event_Dnd_Data_Received *e;
        char ** files;
        int i;

        e = ev;
        files = e->data;

        for (i = 0; i < e->len; i++)
                handle_uri(files[i]);
    }

    static void
    cb_clear(Ewl_Widget *w, void *ev, void *data)
    {
        Ewl_Widget *free;

        free = ewl_widget_name_find("image_box");
        ewl_container_reset(EWL_CONTAINER(free));
    }

    int
    main(int argc, char ** argv)
    {
        Ewl_Widget *win, *box, *s, *o;
        const char *drop_types[] = {"text/uri-list", NULL};
        int ret = 1;

        if (!ecore_init())
        {
                fprintf(stderr, "Unable to initialize Ecore.\n");
                goto SHUTDOWN;
        }

        if (!ecore_file_init())
        {
                fprintf(stderr, "Unable to initialize Ecore_File.\n");
                goto ECORE_SHUTDOWN;
        }

        if (!ewl_init(&argc, argv))
        {
                fprintf(stderr, "Unable to initialize EWL.\n");
                goto ECORE_FILE_SHUTDOWN;
        }

        win = ewl_window_new();
        ewl_window_title_set(EWL_WINDOW(win), "Thumber");
        ewl_window_class_set(EWL_WINDOW(win), "thumber");
        ewl_window_name_set(EWL_WINDOW(win), "thumber");
        ewl_object_fill_policy_set(EWL_OBJECT(win), EWL_FLAG_FILL_ALL);
        ewl_object_size_request(EWL_OBJECT(win), 640, 480);
        ewl_callback_append(win, EWL_CALLBACK_DELETE_WINDOW, cb_window_delete, NULL);
        ewl_callback_append(win, EWL_CALLBACK_KEY_DOWN, cb_key_down, NULL);
        ewl_widget_name_set(win, "main_win");
        ewl_widget_show(win);

        box = ewl_vbox_new();
        ewl_container_child_append(EWL_CONTAINER(win), box);
        ewl_widget_show(box);

        s = ewl_scrollpane_new();
        ewl_container_child_append(EWL_CONTAINER(box), s);
        ewl_widget_show(s);

        o = ewl_freebox_new();
        ewl_container_child_append(EWL_CONTAINER(s), o);
        ewl_dnd_accepted_types_set(o, drop_types);
        ewl_callback_append(o, EWL_CALLBACK_DND_DATA_RECEIVED, cb_dnd_data, NULL);
        ewl_widget_name_set(o, "image_box");
        ewl_widget_show(o);

        s = ewl_hbox_new();
        ewl_object_fill_policy_set(EWL_OBJECT(s), EWL_FLAG_FILL_HFILL |
                                                EWL_FLAG_FILL_VSHRINK);
        ewl_container_child_append(EWL_CONTAINER(box), s);
        ewl_widget_show(s);

        o = ewl_button_new();
        ewl_button_label_set(EWL_BUTTON(o), "clear");
        ewl_callback_append(o, EWL_CALLBACK_CLICKED, cb_clear, NULL);
        ewl_object_fill_policy_set(EWL_OBJECT(o), EWL_FLAG_FILL_SHRINK);
        ewl_container_child_append(EWL_CONTAINER(s), o);
        ewl_widget_show(o);

        if (argc > 1)
                handle_uri(argv[1]);

        ewl_main();
        ret = 0;

    ECORE_FILE_SHUTDOWN:
        ecore_file_shutdown();
    ECORE_SHUTDOWN:
        ecore_shutdown();
    SHUTDOWN:
        return ret;
    }