Tizen Native API  4.0
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

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;
     }

   ecore_imf_context_event_callback_del(en->imf_context, ECORE_IMF_CALLBACK_COMMIT, _ecore_imf_event_commit_cb);
   ecore_imf_context_event_callback_del(en->imf_context, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, _ecore_imf_event_preedit_changed_cb);
   ecore_imf_context_event_callback_del(en->imf_context, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, _ecore_imf_event_delete_surrounding_cb);

   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;
}