Entry - Example of simple editing

As a general overview of Entry we are going to write an, albeit simple, functional editor.

Although intended to show how elm_entry works, this example also makes extensive use of several other widgets. The full code can be found in entry_example.c and in the following lines we'll go through the parts especific to the Entry widget.

The program itself is a simple editor, with a file already set to it, that can be set to autosave or not and allows insertion of emoticons and some formatted text. As of this writing, the capabilities of format edition in the entry are very limited, so a lot of manual work is required to change the current text.

In any case, the program allows some changes by using the buttons on the top of the window and returning focus back to the main entry afterwards.

We'll begin by showing a few structures used throught the program. First, the application owns data that holds the main window and the main entry where the editting happens. Then, an auxiliar structure we'll use later when inserting icons in our text.

typedef struct
{
Evas_Object *win;
Evas_Object *edit_buffer;
} App_Data;
typedef struct
{
Evas_Object *inwin;
Evas_Object *naviframe;
Evas_Object *grid;
Evas_Object *settings;
int size;
int vsize;
int width, height;
const char *emo;
App_Data *ad;
} App_Inwin_Data;

A little convenience function will insert whatever text we need in the buffer at the current cursor's position and set focus back to this entry. This is done mostly because clicking on any button will make them steal focus, which makes writing text more cumbersome.

static void
_edit_buffer_insert(Evas_Object *e, const char *text)
{
elm_entry_entry_insert(e, text);
elm_object_focus_set(e, EINA_TRUE);
}

One of the buttons on the top will trigger an Inwin to open and show us several icons we can insert into the text. We'll jump over most of these functions, but when all the options are chosen, we insert the special markup text that will show the chosen icon in place.

static void
_btn_insert_cb(void *data, Evas_Object *obj, void *event)
{
App_Inwin_Data *aid = data;
const char *size[] = {
"size",
"absize",
"relsize"
};
const char *vsize[] = {
"full",
"ascent"
};
char buf[512];
snprintf(buf, sizeof(buf), "<item %s=%dx%d vsize=%s href=emoticon/%s>"
"</item>", size[aid->size], aid->width, aid->height,
vsize[aid->vsize], aid->emo);
_edit_buffer_insert(aid->ad->edit_buffer, buf);
evas_object_del(aid->inwin);
}

As can be seen in that function, the program lets us add icons to our entry using all the possible configurations for them. That should help to clarify how the different combinations work out by actually seeing them in action.

The same popup window has a page to set the settings of the chosen icon, that is, the size and how the item will be placed within the line.

The size is done with two entries, limitted to accept numbers and a fixed size of characters. Changing the value in this entries will update the icon size in our struct as seen in the next two callbacks.

static void
_width_changed_cb(void *data, Evas_Object *obj, void *event)
{
App_Inwin_Data *aid = data;
aid->width = atoi(elm_object_text_get(obj));
}
static void
_height_changed_cb(void *data, Evas_Object *obj, void *event)
{
App_Inwin_Data *aid = data;
aid->height = atoi(elm_object_text_get(obj));
}

The rest of the options are handled with radio buttons, since only one type of size can be used (`size`, `absize` or `relsize`) and for the vertical sizing it needs to choose between `ascent` and `full`. Depending on which is chosen, the `item` tag is formed accordingly as seen before.

static Evas_Object *
_page_settings_add(Evas_Object *parent, App_Inwin_Data *aid)
{
Evas_Object *box, *sizeopts, *box2, *sizebox, *vsizebox,
*rsize, *rabsize, *rrelsize, *rvfull, *rvascent,
*fwidth, *ewidth, *fheight, *eheight,
*binsert;
char buf[100];
static Elm_Entry_Filter_Accept_Set accept_set = {
.accepted = "0123456789",
.rejected = NULL
};
static Elm_Entry_Filter_Limit_Size limit_size = {
.max_char_count = 5,
.max_byte_count = 0
};
box = elm_box_add(parent);
evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(box, EVAS_HINT_FILL, EVAS_HINT_FILL);
sizeopts = elm_frame_add(parent);
elm_object_text_set(sizeopts, "Size");
evas_object_size_hint_weight_set(sizeopts, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(sizeopts, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box, sizeopts);
evas_object_show(sizeopts);
box2 = elm_box_add(parent);
evas_object_size_hint_weight_set(box2, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(box2, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_object_content_set(sizeopts, box2);
evas_object_show(box2);
sizebox = elm_box_add(parent);
elm_box_horizontal_set(sizebox, EINA_TRUE);
evas_object_size_hint_weight_set(sizebox, EVAS_HINT_EXPAND,
EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(sizebox, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box2, sizebox);
evas_object_show(sizebox);
rsize = elm_radio_add(parent);
elm_object_text_set(rsize, "Scale adjusted (size)");
elm_radio_state_value_set(rsize, 0);
elm_radio_value_pointer_set(rsize, &aid->size);
elm_box_pack_end(sizebox, rsize);
evas_object_show(rsize);
rabsize = elm_radio_add(parent);
elm_object_text_set(rabsize, "Absolute size (absize)");
elm_radio_state_value_set(rabsize, 1);
elm_radio_value_pointer_set(rabsize, &aid->size);
elm_radio_group_add(rabsize, rsize);
elm_box_pack_end(sizebox, rabsize);
evas_object_show(rabsize);
rrelsize = elm_radio_add(parent);
elm_object_text_set(rrelsize, "Relative to line (relsize)");
elm_radio_state_value_set(rrelsize, 2);
elm_radio_value_pointer_set(rrelsize, &aid->size);
elm_radio_group_add(rrelsize, rsize);
elm_box_pack_end(sizebox, rrelsize);
evas_object_show(rrelsize);
vsizebox = elm_box_add(parent);
elm_box_horizontal_set(vsizebox, EINA_TRUE);
evas_object_size_hint_weight_set(vsizebox, EVAS_HINT_EXPAND,
EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(vsizebox, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box2, vsizebox);
evas_object_show(vsizebox);
rvfull = elm_radio_add(parent);
elm_object_text_set(rvfull, "Full height (vsize=full)");
elm_radio_state_value_set(rvfull, 0);
elm_radio_value_pointer_set(rvfull, &aid->vsize);
elm_box_pack_end(vsizebox, rvfull);
evas_object_show(rvfull);
rvascent = elm_radio_add(parent);
elm_object_text_set(rvascent, "Ascent only (vsize=ascent)");
elm_radio_state_value_set(rvascent, 1);
elm_radio_value_pointer_set(rvascent, &aid->vsize);
elm_radio_group_add(rvascent, rvfull);
elm_box_pack_end(vsizebox, rvascent);
evas_object_show(rvascent);

The first of our entries is here. There's something worth mentioning about the way we'll create this one. Normally, any entry regardless of whether is single line or not, will be set to scrollable, but in this case, since we are limitting how many characters can fit in them and we know we don't need scrolling, we are not setting this flag. This makes the entry have virtually no appearance on screen, other than its text. This is because an entry is just that, a box that holds text, and in order to have some frame around it or a background color, another widget needs to provide this. When an entry is scrollable, the same scroller used internally does this. We are using frames here to provide some decoration around, then creating our entries, set them to single line, add our two filters and the callback for when their value change.

fwidth = elm_frame_add(parent);
elm_object_text_set(fwidth, "Width");
evas_object_size_hint_weight_set(fwidth, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(fwidth, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box2, fwidth);
evas_object_show(fwidth);
snprintf(buf, sizeof(buf), "%d", aid->width);
ewidth = elm_entry_add(parent);
elm_entry_single_line_set(ewidth, EINA_TRUE);
elm_entry_markup_filter_append(ewidth, elm_entry_filter_accept_set,
&accept_set);
elm_entry_markup_filter_append(ewidth, elm_entry_filter_limit_size,
&limit_size);
elm_object_text_set(ewidth, buf);
evas_object_size_hint_weight_set(ewidth, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(ewidth, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_object_content_set(fwidth, ewidth);
evas_object_show(ewidth);
evas_object_smart_callback_add(ewidth, "changed", _width_changed_cb, aid);
fheight = elm_frame_add(parent);
elm_object_text_set(fheight, "Height");
evas_object_size_hint_weight_set(fheight, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(fheight, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box2, fheight);
evas_object_show(fheight);
snprintf(buf, sizeof(buf), "%d", aid->height);
eheight = elm_entry_add(parent);
elm_entry_single_line_set(eheight, EINA_TRUE);
elm_entry_markup_filter_append(eheight, elm_entry_filter_accept_set,
&accept_set);
elm_entry_markup_filter_append(eheight, elm_entry_filter_limit_size,
&limit_size);
elm_object_text_set(eheight, buf);
evas_object_size_hint_weight_set(eheight, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(eheight, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_object_content_set(fheight, eheight);
evas_object_show(eheight);
evas_object_smart_callback_add(eheight, "changed", _height_changed_cb, aid);

This function ends with the button that will finally call the item into our editting string.

binsert = elm_button_add(parent);
elm_object_text_set(binsert, "Insert");
elm_box_pack_end(box, binsert);
evas_object_show(binsert);
evas_object_smart_callback_add(binsert, "clicked", _btn_insert_cb, aid);
return box;
}

Then we get to the format edition. Here we can add the `bold` and `emphasis` tags to parts of our text. There's a lot of manual work to know what to do here, since we are not implementing an entire state manager and the entry itself doesn't, yet, support all the needed capabilities to make this simpler. We begin by getting the format we are using in our function from the button pressed.

Next we need to find out if we need to insert an opening or a closing tag. For this, we store the current cursor position and create a selection from this point until the beginning of our text, and then get the selected text to look for any existing format tags in it. This is currently the only way in which we can find out what formats is being used in the entry.

Once we know what tag to insert, we need a second check in the case it was a closing tag. This is because any other closing tag that comes after would be left dangling alone, so we need to remove it to keep the text consistent.

Finally, we clear our fake selections and return the cursor back to the position it had at first, since there is where we want to insert our format.

And finish by calling our convenience function from before, to insert the text at the current cursor and give focus back to the entry.

A checkbox on the top of our program tells us if the text we are editing will autosave or not. In it's `"changed"` callback we get the value from the checkbox and call the elm_entry_autosave_set() function with it. If autosave is set, we also call elm_entry_file_save(). This is so the internal timer used to periodically store to disk our changes is started.

Two more functions to show some cursor playing. Whenever we double click anywhere on our entry, we'll find what word is the cursor placed at and select it. Likewise, for triple clicking, we select the entire line.

And finally, the main window of the program contains the entry where we do all the edition and some helping widgets to change format, add icons or change the autosave flag.

And the main entry of the program. Set to scroll, by default we disable autosave and we'll begin with a file set to it because no file selector is being used here. The file is loaded with #ELM_TEXT_FORMAT_MARKUP_UTF8 so that any format contained in it is interpreted, otherwise the entry would load it as just text, escaping any tags found and no format or icons would be shown. Then we connect to the double and triple click signals and set focus on the entry so we can start typing right away.