Tutorial 3: A Basic Graphical Text Editor with Buttons

In this tutorial you will learn how to build an EFL application with several widgets, including a text box with editable text and two buttons arranged vertically, each with distinct functions.

The end result should look something like this:

Image of what you want to achieve

Prerequisites

You will be basing your text editor on the application you built in the previous guide. The final program (including an expanded text box) looked like this:

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
 
#include <Eina.h>
#include <Efl.h>
#include <Elementary.h>
 
static void
_gui_setup()
{
   Eo *win, *box, *text; //*
 
   win = efl_add(EFL_UI_WIN_CLASS, NULL,
                 efl_ui_win_type_set(efl_added, EFL_UI_WIN_BASIC),
                 efl_text_set(efl_added, "Hello World"),
                 efl_ui_win_autodel_set(efl_added, EINA_TRUE));
 
  box = efl_add(EFL_UI_BOX_CLASS, win,
                efl_content_set(win, efl_added),
                efl_gfx_size_hint_min_set(efl_added, EINA_SIZE2D(360, 240)));
 
   text = efl_add(EFL_UI_TEXT_CLASS, box,
                efl_text_set(efl_added, "Hello World!"),
                efl_pack(box, efl_added));
}
 
EAPI_MAIN void
efl_main(void *data EINA_UNUSED, const Efl_Event *ev EINA_UNUSED)
{
   elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);
 
   _gui_setup();
}
EFL_MAIN()

This program creates a window with a text box 360 pixels wide by 240 pixels high and displays the message "Hello World!" inside it.

Step 1: Making the Text Editable

The first order of business is to make the text box editable. To do this change the name of the text object on line to editor. Don't forget to change on line 23 too.

Next, change the definition of editor from:

[...]
editor = efl_add(EFL_UI_TEXT_CLASS, box,
             efl_text_set(efl_added, "Hello World!"),
             efl_pack(box, efl_added));
[...]

To:

[...]
editor = efl_add(EFL_UI_TEXT_CLASS, box,
                 efl_text_set(efl_added, "Edit me"),
                 efl_ui_text_interactive_editable_set(efl_added, EINA_TRUE),
                 efl_ui_text_scrollable_set(efl_added, EINA_TRUE),
                 efl_pack(box, efl_added));
[...]

By adding the efl_ui_text_interactive_editable_set() you make the text box editable. With efl_ui_text_scrollable_set() users can type more text than is available in the visible area, as they can scroll with the cursor or the Home and End keys.

NOTE: [...] in a Code Block indicates existing code which has been excluded for the sake of brevity. There is no need to type [...] into your program.

Step 2: Adding Buttons

Next you're going to add two buttons. One will be an About button which displays text on the command line when pressed. The other will exit the program.

To add buttons, first add a new object to the variable declarations on line 12:

[...]
Eo *win, *box, *editor, *button;
[...]

You can then use the EFL_UI_BUTTON_CLASS macro to define your buttons:

[...]
button = efl_add(EFL_UI_BUTTON_CLASS, box,
                 efl_text_set(efl_added, "About"),
                 efl_pack(box, efl_added),
                 efl_event_callback_add(efl_added, EFL_UI_EVENT_CLICKED,
                                        _gui_about_clicked_cb, efl_added));
button = efl_add(EFL_UI_BUTTON_CLASS, box,
                 efl_text_set(efl_added, "Quit"),
                 efl_pack(box, efl_added),
                 efl_event_callback_add(efl_added, EFL_UI_EVENT_CLICKED,
                                        _gui_quit_clicked_cb, efl_added));
[...]

Use efl_text_set() to set the buttons' labels and efl_pack() to fit the buttons to the box. The efl_event_callback_add() is used to describe the event that will trigger an action. In this case, the action will be triggered when the button is clicked, as specified by EFL_UI_EVENT_CLICKED. The actions the buttons trigger are defined by functions which you will have to add to your program.

In the code above line 6 tells your application to run the function _gui_about_clicked_cb() when users click the About button and the _gui_quit_clicked_cb() function when they click the Quit button. Add those two functions to your program (you can leave them empty for the moment) so you can compile it.

Your program should now look like this:

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
 
#include <Eina.h>
#include <Efl.h>
#include <Elementary.h>
 
static void
_gui_about_clicked_cb()
{
  /* code */
}
 
static void
_gui_quit_clicked_cb()
{
  /* code */
}
 
static void
_gui_setup()
{
   Eo *win, *box, *editor, *button;
 
   win = efl_add(EFL_UI_WIN_CLASS, NULL,
                 efl_ui_win_type_set(efl_added, EFL_UI_WIN_BASIC),
                 efl_text_set(efl_added, "Basic Editor"),
                 efl_ui_win_autodel_set(efl_added, EINA_TRUE));
 
   box = efl_add(EFL_UI_BOX_CLASS, win,
                efl_content_set(win, efl_added),
                efl_gfx_size_hint_min_set(efl_added, EINA_SIZE2D(360, 240)));
 
   editor = efl_add(EFL_UI_TEXT_CLASS, box,
               efl_text_set(efl_added, "Edit me"),
               efl_ui_text_interactive_editable_set(efl_added, EINA_TRUE),
               efl_ui_text_scrollable_set(efl_added, EINA_TRUE),
               efl_pack(box, efl_added));
 
   button = efl_add(EFL_UI_BUTTON_CLASS, box,
                    efl_text_set(efl_added, "About"),
                    efl_pack(box, efl_added),
                    efl_event_callback_add(efl_added, EFL_UI_EVENT_CLICKED,
                                           _gui_about_clicked_cb, efl_added));
   button = efl_add(EFL_UI_BUTTON_CLASS, box,
                    efl_text_set(efl_added, "Quit"),
                    efl_pack(box, efl_added),
                    efl_event_callback_add(efl_added, EFL_UI_EVENT_CLICKED,
                                           _gui_quit_clicked_cb, efl_added));
}
 
EAPI_MAIN void
efl_main(void *data EINA_UNUSED, const Efl_Event *ev EINA_UNUSED)
{
   elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);
 
   _gui_setup();
}
EFL_MAIN()

If you now compile and run this program, you will see something like this:

Editable text box with buttons

Note how Enlightenment splits the space in the box equally between the text box and the two buttons, assigning about a third to each. You'll learn how to organise and assign space yourself in the next section.

Step 3: Organising Widgets

So far you've bundled all your elements into single box. Next you're going to create a new box exclusively for the buttons and place them side by side instead of one on top of the other. This looks much more tidy.

Add a new object called hboxto the the object declaration line in _gui_setup():

[...]
   Eo *win, *box, *hbox, *editor, *button;
[...]

Now write its definition between the definition of editor and the definition of your first button:

[...]
hbox = efl_add(EFL_UI_BOX_CLASS, box,
              efl_ui_direction_set(efl_added, EFL_UI_DIR_HORIZONTAL),
              efl_gfx_size_hint_weight_set(efl_added, 1.0, 0.1),
              efl_pack(box, efl_added));
[...]

This creates a new box, with the difference that the widgets you put in it will be laid out horizontally (efl_ui_direction_set(efl_added, EFL_UI_DIR_HORIZONTAL)) instead of vertically, which is the default.

Note that hboxis placed within box. The editor text box is also in box. This means they're both sharing a container. You use the the two parameters in efl_gfx_size_hint_weight_set() to establish how much space each gets. The first value (1.0) sets how much space the object takes up horizontally. As box is using the default layout, it is stacking widgets one on top of the other. As a result, no matter what value you enter here (values can range from 0.0 to 1.0) both editor and hbox will stretch to the full width of the box container.

The second value, 0.1, establishes how much space the hbox widget takes up vertically. As widgets in box are stacked vertically, changing this value will change its size and by extension that available to the buttons it contains. For example if you set it to 1.0, it will take up the full space it would be assigned by default which is half the height of the containing box : Remember box now contains two widgets and will assign by default half the space available to each. Setting it to 0.1 makes the box and any buttons it contains slightly taller than the bare minimum. Anything value in between will assign varying heights.

Next, change the buttons so their parent is hboxinstead of box:

[...]
button = efl_add(EFL_UI_BUTTON_CLASS, hbox,
                 efl_text_set(efl_added, "About"),
                 efl_pack(hbox, efl_added),
                 efl_event_callback_add(efl_added, EFL_UI_EVENT_CLICKED,
                                        _gui_about_clicked_cb, efl_added));
 
button = efl_add(EFL_UI_BUTTON_CLASS, hbox,
                 efl_text_set(efl_added, "Quit"),
                 efl_pack(hbox, efl_added),
                 efl_event_callback_add(efl_added, EFL_UI_EVENT_CLICKED,
                                        _gui_quit_clicked_cb, efl_added));
[...]

When you compile and run this program, you will see something like this:

Horizontal buttons

Step 4: Adding Button Functionality

All that's missing now is adding actions that get executed when the buttons are pressed. This means filling out the _gui_about_clicked_cb() and _gui_quit_clicked_cb() you created earlier:

[...]
static void
_gui_about_clicked_cb(void *data, const Efl_Event *event EINA_UNUSED)
{
   printf("Clicked About\n");
}
 
static void
_gui_quit_clicked_cb(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED)
{
   efl_exit(0);
}
[...]

There is nothing complex about this code: _gui_about_clicked_cb() just prints a string to the command line and _gui_quit_clicked_cb() calls EFL's inbuilt efl_exit() function, which closes EFL applications.

The complete program is listed below::

The Complete "basic-editor.c" Program

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
 
#include <Eina.h>
#include <Efl.h>
#include <Elementary.h>
 
static void
_gui_about_clicked_cb(void *data, const Efl_Event *event EINA_UNUSED)
{
   Eo *button = data;
 
   printf("Clicked About\n");
}
 
static void
_gui_quit_clicked_cb(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED)
{
   efl_exit(0);
}
 
static void
_gui_setup()
{
   Eo *win, *box, *hbox, *editor, *button;
 
   win = efl_add(EFL_UI_WIN_CLASS, NULL,
                 efl_ui_win_type_set(efl_added, EFL_UI_WIN_BASIC),
                 efl_text_set(efl_added, "Hello World"),
                 efl_ui_win_autodel_set(efl_added, EINA_TRUE));
 
   box = efl_add(EFL_UI_BOX_CLASS, win,
                efl_content_set(win, efl_added),
                efl_gfx_size_hint_min_set(efl_added, EINA_SIZE2D(360, 240)));
 
   editor = efl_add(EFL_UI_TEXT_CLASS, box,
                    efl_text_set(efl_added, "Edit me"),
                    efl_ui_text_interactive_editable_set(efl_added, EINA_TRUE),
                    efl_ui_text_scrollable_set(efl_added, EINA_TRUE),
                    efl_pack(box, efl_added));
 
   hbox = efl_add(EFL_UI_BOX_CLASS, box,
                 efl_ui_direction_set(efl_added, EFL_UI_DIR_HORIZONTAL),
                 efl_gfx_size_hint_weight_set(efl_added, 1.0, 0.1),
                 efl_pack(box, efl_added));
 
   button = efl_add(EFL_UI_BUTTON_CLASS, hbox,
                    efl_text_set(efl_added, "About"),
                    efl_pack(hbox, efl_added),
                    efl_event_callback_add(efl_added, EFL_UI_EVENT_CLICKED,
                                           _gui_about_clicked_cb, efl_added));
 
   button = efl_add(EFL_UI_BUTTON_CLASS, hbox,
                    efl_text_set(efl_added, "Quit"),
                    efl_pack(hbox, efl_added),
                    efl_event_callback_add(efl_added, EFL_UI_EVENT_CLICKED,
                                           _gui_quit_clicked_cb, efl_added));
}
 
EAPI_MAIN void
efl_main(void *data EINA_UNUSED, const Efl_Event *ev EINA_UNUSED)
{
   elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);
 
   _gui_setup();
}
EFL_MAIN()

Compiling and Running

The instructions of the [Compile and Run section in the EFL Installation Guide](TODO: efl-installation-guide.md#Compiling) will create an executable file called "basic-editor". Run your program with:

./basic-editor

You will see something like what is shown below.

A basic GUI-based text editor with buttons.

Clicking on the About button will print the message "Clicked About" on the command line. Clicking Quit will close the program.

If anything goes wrong check out the Troubleshooting section at the end of this page.

Things to Try

As it stands, the editor is not terribly useful. Users cannot save their work for instance. This would certainly be something worth developing further, by implementing Open File and Save buttons.

Another issue is that when you reach the right-most edge of the window and keep typing any text you've already typed scrolls left and disappears. The cursor does not jump to a new line. Also, there is no scrollbar, which means that the only way to move back and forth along the text is by moving the cursor with the arrow keys or the Home and End keys.

You will learn how to solve all of the above issues in [the next tutorial](TODO: LINK TO FULL EDITOR TUTORIAL).

Summary

The most important lessons you can take away from this tutorial are:

  1. Adding new functions to widgets changes their behavior. Adding efl_ui_text_interactive_editable_set(efl_added, EINA_TRUE), for example, suddenly makes the text in a text box editable.
  2. Buttons need callback functions that implement the actions that should be carried out when they are clicked. These callback functions are passed as a parameters to the buttons' efl_event_callback_add() functions.
  3. Using weight hinting allows you to assign space to widgets with great precision.

Troubleshooting

When you try compile the program above, you may see the following error:

basic-editor.c: In function ‘_gui_quit_clicked_cb’:
basic-editor.c:18:14: error: macro "efl_exit" passed 1 arguments, but takes just 0
    efl_exit(0);
              ^
basic-editor.c:18:4: error: ‘efl_exit’ undeclared (first use in this function)
    efl_exit(0);
    ^
basic-editor.c:18:4: note: each undeclared identifier is reported only once for each function it appears in

This happens if you're using a slightly older version of EFL libraries. Resolve the issue by downloading and using the latest version of EFL.

It this isn't possible you can avoid this problem by changing the line:

[...]
efl_exit(0);
[...]

in _gui_quit_clicked_cb() to:

[...]
efl_exit();
[...]

Resources

  1. [A list of useful widgets](TODO: widgets.md)
  2. Download the basic-editor.c from here

What's Next

In the [next guide](TODO: Insert correct link to next guide) you will learn how to make a a much more advanced text editor. The editor will wrap overlapping text and allow users to open and save text files.