ecore_imf - How to handle preedit and commit string from Input Method Framework

This example demonstrates how to connect input method framework and handle preedit and commit string from input method framework.

To input Chinese, Japanese, Korean and other complex languages, the editor should be connected with input method framework.

How to initialize and shutdown ecore imf module

- ecore_imf_init() should be called to initialize and load immodule.
- ecore_imf_shutdown() is used for shutdowning and unloading immodule.

How to create input context and register pre-edit and commit event handler

Each entry should have each input context to connect with input service framework. Key event is processed by input method engine. The result is notified to application through ECORE_IMF_CALLBACK_PREEDIT_CHANGED and ECORE_IMF_CALLBACK_COMMIT event.

The full example follows.

#include <Ecore.h>
#include <Ecore_Evas.h>
#include <Ecore_IMF.h>
#include <Ecore_IMF_Evas.h>
#include <Evas.h>
#include <stdio.h>
#define WIDTH 480
#define HEIGHT 800
typedef struct _Entry Entry;
struct _Entry
{
Evas_Object *rect;
Evas_Object *txt_obj;
Evas_Textblock_Style *txt_style;
Evas_Textblock_Cursor *cursor;
Evas_Textblock_Cursor *preedit_start;
Evas_Textblock_Cursor *preedit_end;
Ecore_IMF_Context *imf_context;
Eina_Bool have_preedit : 1;
};
static void _imf_cursor_info_set(Entry *en);
static void
_mouse_down_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *event_info)
{
Entry *en = data;
Evas_Event_Mouse_Down *ev = event_info;
if (!en) return;
if (en->imf_context)
{
Ecore_IMF_Event_Mouse_Down ecore_ev;
ecore_imf_evas_event_mouse_down_wrap(ev, &ecore_ev);
if (ecore_imf_context_filter_event(en->imf_context,
ECORE_IMF_EVENT_MOUSE_DOWN,
(Ecore_IMF_Event *)&ecore_ev))
return;
// ecore_imf_context_reset should be called before calculating new cursor position
ecore_imf_context_reset(en->imf_context);
}
// calculate new cursor position
}
static void
_mouse_up_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *event_info)
{
Entry *en = data;
Evas_Event_Mouse_Up *ev = event_info;
if (!en) return;
if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD)
{
_imf_cursor_info_set(en);
return;
}
if (en->imf_context)
{
Ecore_IMF_Event_Mouse_Up ecore_ev;
ecore_imf_evas_event_mouse_up_wrap(ev, &ecore_ev);
if (ecore_imf_context_filter_event(en->imf_context,
ECORE_IMF_EVENT_MOUSE_UP,
(Ecore_IMF_Event *)&ecore_ev))
return;
}
if (en->rect)
{
if (evas_object_focus_get(en->rect))
{
// notify cursor information
_imf_cursor_info_set(en);
}
else
evas_object_focus_set(en->rect, EINA_TRUE);
}
}
static void
_entry_focus_in_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *event_info EINA_UNUSED)
{
Entry *en = data;
if (!en) return;
if (en->imf_context)
ecore_imf_context_focus_in(en->imf_context);
// notify the cursor information
_imf_cursor_info_set(en);
}
static void
_entry_focus_out_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *event_info EINA_UNUSED)
{
Entry *en = data;
if (!en) return;
if (en->imf_context)
{
// ecore_imf_context_reset should be called for flushing the preedit string in focus-out event handler
ecore_imf_context_reset(en->imf_context);
ecore_imf_context_focus_out(en->imf_context);
}
}
static void
_canvas_focus_in_cb(void *data EINA_UNUSED, Evas *e, void *event_info EINA_UNUSED)
{
Entry *en;
Evas_Object *obj = evas_focus_get(e);
if (!obj) return;
en = evas_object_data_get(obj, "Entry");
if (en)
_entry_focus_in_cb(en, NULL, NULL, NULL);
}
static void
_canvas_focus_out_cb(void *data EINA_UNUSED, Evas *e, void *event_info EINA_UNUSED)
{
Entry *en;
Evas_Object *obj = evas_focus_get(e);
if (!obj) return;
en = evas_object_data_get(obj, "Entry");
if (en)
_entry_focus_out_cb(en, NULL, NULL, NULL);
}
static void
_imf_cursor_info_set(Entry *en)
{
Evas_Coord x, y, w, h;
Evas_Coord cx, cy, cw, ch; // cursor geometry
int cursor_pos; // cursor position in chars (Not bytes)
Evas_BiDi_Direction dir;
if (!en) return;
// get cursor geometry
if (en->txt_obj)
evas_object_geometry_get(en->txt_obj, &x, &y, &w, &h);
if (en->cursor && en->imf_context)
{
evas_textblock_cursor_geometry_get(en->cursor, &cx, &cy, &cw, &ch, &dir, EVAS_TEXTBLOCK_CURSOR_BEFORE);
// get cursor position
cursor_pos = evas_textblock_cursor_pos_get(en->cursor);
ecore_imf_context_cursor_position_set(en->imf_context, cursor_pos);
ecore_imf_context_cursor_location_set(en->imf_context, x + cx, y + cy, cw, ch);
ecore_imf_context_bidi_direction_set(en->imf_context, (Ecore_IMF_BiDi_Direction)dir);
}
}
static void
_preedit_del(Entry *en)
{
if (!en || !en->have_preedit) return;
if (!en->preedit_start || !en->preedit_end) return;
if (!evas_textblock_cursor_compare(en->preedit_start, en->preedit_end)) return;
// delete the preedit characters
evas_textblock_cursor_range_delete(en->preedit_start, en->preedit_end);
}
static void
_preedit_clear(Entry *en)
{
if (en->preedit_start)
{
evas_textblock_cursor_free(en->preedit_start);
en->preedit_start = NULL;
}
if (en->preedit_end)
{
evas_textblock_cursor_free(en->preedit_end);
en->preedit_end = NULL;
}
en->have_preedit = EINA_FALSE;
}
static Eina_Bool
_ecore_imf_retrieve_surrounding_cb(void *data, Ecore_IMF_Context *ctx EINA_UNUSED, char **text, int *cursor_pos)
{
// This callback will be called when the Input Method Context module requests the surrounding context.
Entry *en = data;
const char *str;
if (!en) return EINA_FALSE;
str = evas_object_textblock_text_markup_get(en->txt_obj);
if (text)
*text = str ? strdup(str) : strdup("");
// get the current position of cursor
if (cursor_pos && en->cursor)
*cursor_pos = evas_textblock_cursor_pos_get(en->cursor);
return EINA_TRUE;
}
static void
_ecore_imf_event_delete_surrounding_cb(void *data, Ecore_IMF_Context *ctx EINA_UNUSED, void *event_info)
{
// called when the input method needs to delete all or part of the context surrounding the cursor
Entry *en = data;
Ecore_IMF_Event_Delete_Surrounding *ev = event_info;
Evas_Textblock_Cursor *del_start, *del_end;
int cursor_pos;
if ((!en) || (!ev) || (!en->cursor)) return;
// get the current cursor position
cursor_pos = evas_textblock_cursor_pos_get(en->cursor);
// start cursor position to be deleted
del_start = evas_object_textblock_cursor_new(en->txt_obj);
evas_textblock_cursor_pos_set(del_start, cursor_pos + ev->offset);
// end cursor position to be deleted
del_end = evas_object_textblock_cursor_new(en->txt_obj);
evas_textblock_cursor_pos_set(del_end, cursor_pos + ev->offset + ev->n_chars);
// implement function to delete character(s) from 'cursor_pos+ev->offset' cursor position to 'cursor_pos + ev->offset + ev->n_chars'
evas_textblock_cursor_range_delete(del_start, del_end);
evas_textblock_cursor_free(del_start);
evas_textblock_cursor_free(del_end);
}
static void
_ecore_imf_event_commit_cb(void *data, Ecore_IMF_Context *ctx EINA_UNUSED, void *event_info)
{
Entry *en = data;
char *commit_str = (char *)event_info;
if (!en) return;
// delete preedit string
_preedit_del(en);
_preedit_clear(en);
printf("commit string : %s\n", commit_str);
// insert the commit string in the editor
if (en->cursor && commit_str)
evas_object_textblock_text_markup_prepend(en->cursor, commit_str);
// notify the cursor information
_imf_cursor_info_set(en);
return;
}
static void
_ecore_imf_event_preedit_changed_cb(void *data, Ecore_IMF_Context *ctx, void *event_info EINA_UNUSED)
{
// example how to get preedit string
Entry *en = data;
char *preedit_string;
int cursor_pos;
Eina_List *attrs = NULL;
Eina_List *l;
Ecore_IMF_Preedit_Attr *attr;
Ecore_IMF_Context *imf_context = ctx;
int preedit_start_pos, preedit_end_pos;
int i;
Eina_Bool preedit_end_state = EINA_FALSE;
if (!en || !en->cursor) return;
// get preedit string and attributes
ecore_imf_context_preedit_string_with_attributes_get(imf_context, &preedit_string, &attrs, &cursor_pos);
printf("preedit string : %s\n", preedit_string);
if (!strcmp(preedit_string, ""))
preedit_end_state = EINA_TRUE;
// delete preedit
_preedit_del(en);
preedit_start_pos = evas_textblock_cursor_pos_get(en->cursor);
// insert preedit character(s)
if (strlen(preedit_string) > 0)
{
if (attrs)
{
EINA_LIST_FOREACH(attrs, l, attr)
{
if (attr->preedit_type == ECORE_IMF_PREEDIT_TYPE_SUB1) // style type
{
// apply appropriate style such as underline
}
else if (attr->preedit_type == ECORE_IMF_PREEDIT_TYPE_SUB2 || attr->preedit_type == ECORE_IMF_PREEDIT_TYPE_SUB3)
{
// apply appropriate style such as underline
}
}
// insert code to display preedit string in your editor
evas_object_textblock_text_markup_prepend(en->cursor, preedit_string);
}
}
if (!preedit_end_state)
{
// set preedit start cursor
if (!en->preedit_start)
en->preedit_start = evas_object_textblock_cursor_new(en->txt_obj);
evas_textblock_cursor_copy(en->cursor, en->preedit_start);
// set preedit end cursor
if (!en->preedit_end)
en->preedit_end = evas_object_textblock_cursor_new(en->txt_obj);
evas_textblock_cursor_copy(en->cursor, en->preedit_end);
preedit_end_pos = evas_textblock_cursor_pos_get(en->cursor);
for (i = 0; i < (preedit_end_pos - preedit_start_pos); i++)
{
evas_textblock_cursor_char_prev(en->preedit_start);
}
en->have_preedit = EINA_TRUE;
// set cursor position
evas_textblock_cursor_pos_set(en->cursor, preedit_start_pos + cursor_pos);
}
// notify the cursor information
_imf_cursor_info_set(en);
EINA_LIST_FREE(attrs, attr)
free(attr);
free(preedit_string);
}
static void
_key_down_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *event_info)
{
Entry *en = data;
Evas_Event_Key_Down *ev = event_info;
Eina_Bool control, alt, shift;
if ((!en) || (!ev->key) || (!en->cursor)) return;
if (en->imf_context)
{
Ecore_IMF_Event_Key_Down ecore_ev;
ecore_imf_evas_event_key_down_wrap(ev, &ecore_ev);
if (ecore_imf_context_filter_event(en->imf_context,
ECORE_IMF_EVENT_KEY_DOWN,
(Ecore_IMF_Event *)&ecore_ev))
return;
}
control = evas_key_modifier_is_set(ev->modifiers, "Control");
alt = evas_key_modifier_is_set(ev->modifiers, "Alt");
shift = evas_key_modifier_is_set(ev->modifiers, "Shift");
(void)alt;
(void)shift;
if (!strcmp(ev->key, "BackSpace"))
{
if (evas_textblock_cursor_char_prev(en->cursor))
{
evas_textblock_cursor_char_delete(en->cursor);
// notify the cursor information
_imf_cursor_info_set(en);
}
return;
}
else if (!strcmp(ev->key, "Delete") ||
(!strcmp(ev->key, "KP_Delete") && !ev->string))
{
// FILLME
}
else if ((control) && (!strcmp(ev->key, "v")))
{
// ctrl + v
// FILLME
}
else if ((control) && (!strcmp(ev->key, "a")))
{
// ctrl + a
// FILLME
}
else if ((control) && (!strcmp(ev->key, "A")))
{
// ctrl + A
// FILLME
}
else if ((control) && ((!strcmp(ev->key, "c") || (!strcmp(ev->key, "Insert")))))
{
// ctrl + c
// FILLME
}
else if ((control) && ((!strcmp(ev->key, "x") || (!strcmp(ev->key, "m")))))
{
// ctrl + x
// FILLME
}
else if ((control) && (!strcmp(ev->key, "z")))
{
// ctrl + z (undo)
// FILLME
}
else if ((control) && (!strcmp(ev->key, "y")))
{
// ctrl + y (redo)
// FILLME
}
else if ((!strcmp(ev->key, "Return")) || (!strcmp(ev->key, "KP_Enter")))
{
// FILLME
}
else
{
if (ev->string)
{
printf("key down string : %s\n", ev->string);
evas_object_textblock_text_markup_prepend(en->cursor, ev->string);
}
}
// notify the cursor information
_imf_cursor_info_set(en);
}
static void
_key_up_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *event_info)
{
Entry *en = data;
Evas_Event_Key_Up *ev = event_info;
if (!en) return;
if (en->imf_context)
{
Ecore_IMF_Event_Key_Up ecore_ev;
ecore_imf_evas_event_key_up_wrap(ev, &ecore_ev);
if (ecore_imf_context_filter_event(en->imf_context,
ECORE_IMF_EVENT_KEY_UP,
(Ecore_IMF_Event *)&ecore_ev))
return;
}
}
static void
create_input_field(Evas *evas, Entry *en, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
if (!en) return;
en->have_preedit = EINA_FALSE;
en->preedit_start = NULL;
en->preedit_end = NULL;
// create the background for text input field
en->rect = evas_object_rectangle_add(evas);
evas_object_color_set(en->rect, 150, 150, 150, 255); // gray color
evas_object_move(en->rect, x, y);
evas_object_resize(en->rect, w, h);
evas_object_show(en->rect);
evas_object_data_set(en->rect, "Entry", en);
// create text object for displaying text
en->txt_obj = evas_object_textblock_add(evas);
evas_object_color_set(en->txt_obj, 0, 0, 0, 255);
evas_object_pass_events_set(en->txt_obj, EINA_TRUE);
evas_object_move(en->txt_obj, x, y);
evas_object_resize(en->txt_obj, w, h);
evas_object_show(en->txt_obj);
// set style on textblock
static const char *style_buf =
"DEFAULT='font=Sans font_size=30 color=#000 text_class=entry'"
"newline='br'"
"b='+ font=Sans:style=bold'";
en->txt_style = evas_textblock_style_new();
evas_textblock_style_set(en->txt_style, style_buf);
evas_object_textblock_style_set(en->txt_obj, en->txt_style);
// create cursor
en->cursor = evas_object_textblock_cursor_new(en->txt_obj);
// create input context
const char *default_id = ecore_imf_context_default_id_get();
if (!default_id)
{
fprintf(stderr, "Can't create ecore_imf_context\n");
return;
}
en->imf_context = ecore_imf_context_add(default_id);
ecore_imf_context_client_canvas_set(en->imf_context, evas);
// register key event handler
evas_object_event_callback_add(en->rect, EVAS_CALLBACK_KEY_DOWN, _key_down_cb, en);
evas_object_event_callback_add(en->rect, EVAS_CALLBACK_KEY_UP, _key_up_cb, en);
// register mouse event handler
evas_object_event_callback_add(en->rect, EVAS_CALLBACK_MOUSE_DOWN, _mouse_down_cb, en);
evas_object_event_callback_add(en->rect, EVAS_CALLBACK_MOUSE_UP, _mouse_up_cb, en);
// register focus event handler
evas_object_event_callback_add(en->rect, EVAS_CALLBACK_FOCUS_IN, _entry_focus_in_cb, en);
evas_object_event_callback_add(en->rect, EVAS_CALLBACK_FOCUS_OUT, _entry_focus_out_cb, en);
// register retrieve surrounding callback
ecore_imf_context_retrieve_surrounding_callback_set(en->imf_context, _ecore_imf_retrieve_surrounding_cb, en);
// register commit event callback
ecore_imf_context_event_callback_add(en->imf_context, ECORE_IMF_CALLBACK_COMMIT, _ecore_imf_event_commit_cb, en);
// register preedit changed event handler
ecore_imf_context_event_callback_add(en->imf_context, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, _ecore_imf_event_preedit_changed_cb, en);
// register surrounding delete event callback
ecore_imf_context_event_callback_add(en->imf_context, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, _ecore_imf_event_delete_surrounding_cb, en);
}
static void
delete_input_field(Entry *en)
{
if (!en) return;
if (en->rect)
{
evas_object_del(en->rect);
en->rect = NULL;
}
if (en->cursor)
{
evas_textblock_cursor_free(en->cursor);
en->cursor = NULL;
}
if (en->preedit_start)
{
evas_textblock_cursor_free(en->preedit_start);
en->preedit_start = NULL;
}
if (en->preedit_end)
{
evas_textblock_cursor_free(en->preedit_end);
en->preedit_end = NULL;
}
if (en->txt_obj)
{
evas_object_del(en->txt_obj);
en->txt_obj = NULL;
}
if (en->txt_style)
{
evas_textblock_style_free(en->txt_style);
en->txt_style = NULL;
}
if (en->imf_context)
{
ecore_imf_context_del(en->imf_context);
en->imf_context = NULL;
}
}
int
main(void)
{
Ecore_Evas *ee;
Evas *evas;
Entry en1, en2;
if (!ecore_evas_init())
{
fprintf(stderr, "failed to call ecore_evas_init()\n");
return EXIT_FAILURE;
}
ecore_imf_init();
// create a new window, with size=WIDTHxHEIGHT and default engine
ee = ecore_evas_new(NULL, 0, 0, WIDTH, HEIGHT, NULL);
if (!ee)
{
fprintf(stderr, "failed to call ecore_evas_new\n");
return EXIT_FAILURE;
}
ecore_evas_show(ee);
// get the canvas off just-created window
evas = ecore_evas_get(ee);
if (!evas)
{
fprintf(stderr, "failed to call ecore_evas_get\n");
return EXIT_FAILURE;
}
// create input field rectangle
Evas_Object *bg = evas_object_rectangle_add(evas);
evas_object_move(bg, 0, 0);
evas_object_resize(bg, WIDTH, HEIGHT);
evas_object_color_set(bg, 255, 255, 255, 255);
evas_object_show(bg);
// register canvas focus in/out event handler
evas_event_callback_add(evas, EVAS_CALLBACK_CANVAS_FOCUS_IN, _canvas_focus_in_cb, NULL);
evas_event_callback_add(evas, EVAS_CALLBACK_CANVAS_FOCUS_OUT, _canvas_focus_out_cb, NULL);
memset(&en1, 0, sizeof(en1));
memset(&en2, 0, sizeof(en2));
// create input field 1
create_input_field(evas, &en1, 40, 60, 400, 80);
// create input field 2
create_input_field(evas, &en2, 40, 180, 400, 80);
// give focus to input field 1
evas_object_focus_set(en1.rect, EINA_TRUE);
ecore_main_loop_begin(); // begin mainloop
delete_input_field(&en1); // delete input field 1
delete_input_field(&en2); // delete input field 2
evas_event_callback_del_full(evas, EVAS_CALLBACK_CANVAS_FOCUS_IN, _canvas_focus_in_cb, NULL);
evas_event_callback_del_full(evas, EVAS_CALLBACK_CANVAS_FOCUS_OUT, _canvas_focus_out_cb, NULL);
ecore_evas_free(ee);
ecore_imf_shutdown();
ecore_evas_shutdown();
return 0;
}