diff options
author | Youngbok Shin <youngb.shin@samsung.com> | 2018-08-20 07:21:53 -0400 |
---|---|---|
committer | Mike Blumenkrantz <zmike@samsung.com> | 2018-08-20 10:29:32 -0400 |
commit | 517018e00897f61136418861563a49144a5fe39a (patch) | |
tree | 694c299402631380a10a7ad6ddbebfbb774e7e91 /src/lib/evas/canvas/evas_object_textblock.c | |
parent | 8da56ac873d5bb083b7cfe08aeefdaa2ad9a4b99 (diff) | |
download | efl-517018e00897f61136418861563a49144a5fe39a.tar.gz |
evas textblock: add/apply cursor cluster APIs based on grapheme cluster
Summary:
Add a feature for moving cursor over a grapheme cluster.
It is applied to edje_entry.c and elm_entry.c for improving
cursor handling just like other modern text editors. ex) gedit
The patch on Evas needs to update libunibreak library.
So, the patch will update libunibreak, too.
@feature
Test Plan:
1. Put "ഹലോ" in your entry.
2. Your cursor can reach at the end of text from the beginning
only in 2 right key event with this feature.
Reviewers: raster, cedric, jpeg, herdsman, zmike, devilhorns
Reviewed By: herdsman, zmike
Subscribers: #reviewers, #committers, zmike, bowonryu, woohyun
Tags: #efl
Differential Revision: https://phab.enlightenment.org/D5490
Diffstat (limited to 'src/lib/evas/canvas/evas_object_textblock.c')
-rw-r--r-- | src/lib/evas/canvas/evas_object_textblock.c | 276 |
1 files changed, 253 insertions, 23 deletions
diff --git a/src/lib/evas/canvas/evas_object_textblock.c b/src/lib/evas/canvas/evas_object_textblock.c index f523f131e9..16f717ce41 100644 --- a/src/lib/evas/canvas/evas_object_textblock.c +++ b/src/lib/evas/canvas/evas_object_textblock.c @@ -77,6 +77,7 @@ #include "linebreak.h" #include "wordbreak.h" +#include "graphemebreak.h" #include "evas_filter.h" #include "efl_canvas_filter_internal.eo.h" @@ -9271,20 +9272,122 @@ _efl_canvas_text_efl_text_cursor_cursor_word_end(Eo *eo_obj, Efl_Canvas_Text_Dat efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); } -EAPI Eina_Bool -evas_textblock_cursor_char_next(Efl_Text_Cursor_Cursor *cur) +static char * +_evas_textblock_grapheme_breaks_new(Evas_Object_Textblock_Item *it, size_t len) { - int ind; + char *grapheme_breaks = NULL; + const char *lang = (it->format->font.fdesc) ? it->format->font.fdesc->lang : ""; + + grapheme_breaks = malloc(len); + if (!grapheme_breaks) return NULL; + + set_graphemebreaks_utf32((const utf32_t *) + eina_ustrbuf_string_get( + it->text_node->unicode), + len, lang, grapheme_breaks); + + return grapheme_breaks; +} + +static size_t +_evas_textblock_cursor_cluster_pos_get(Evas_Textblock_Cursor *cur, Eina_Bool inc) +{ + Evas_Object_Textblock_Paragraph *par; + Efl_Canvas_Text_Data *o; + size_t cur_pos = cur->pos; + size_t ret = cur->pos; + + if (!inc) cur_pos--; + + if (!cur->node->par) + { + o = efl_data_scope_get(cur->obj, MY_CLASS); + if (o) _relayout_if_needed(cur->obj, o); + } + + par = cur->node->par; + + if (par) + { + Eina_List *l; + Evas_Object_Textblock_Item *it, *last_it = NULL; + EINA_LIST_FOREACH(par->logical_items, l, it) + { + if (it->text_pos > cur_pos) + { + if (!last_it) last_it = it; + break; + } + last_it = it; + } + + if (last_it) + { + it = last_it; + if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) + { + size_t len = eina_ustrbuf_length_get(it->text_node->unicode); + char *grapheme_breaks = _evas_textblock_grapheme_breaks_new(it, len); + + if (grapheme_breaks) + { + size_t grapheme_breaks_index = cur_pos; + + if (inc) + { + while ((grapheme_breaks_index < len) && + (grapheme_breaks[grapheme_breaks_index] != GRAPHEMEBREAK_BREAK)) + { + grapheme_breaks_index++; + } + + ret = grapheme_breaks_index + 1; + } + else + { + while ((grapheme_breaks_index > 0) && + (grapheme_breaks[grapheme_breaks_index - 1] != GRAPHEMEBREAK_BREAK)) + { + grapheme_breaks_index--; + } + + ret = grapheme_breaks_index; + } + + free(grapheme_breaks); + } + } + } + } + + return ret; +} + +static Eina_Bool +_evas_textblock_cursor_next(Evas_Textblock_Cursor *cur, Eina_Bool per_cluster) +{ + Evas_Object_Protected_Data *obj; const Eina_Unicode *text; + int ind; if (!cur) return EINA_FALSE; - Evas_Object_Protected_Data *obj = efl_data_scope_get(cur->obj, EFL_CANVAS_OBJECT_CLASS); - evas_object_async_block(obj); TB_NULL_CHECK(cur->node, EINA_FALSE); + obj = efl_data_scope_get(cur->obj, EFL_CANVAS_OBJECT_CLASS); + evas_object_async_block(obj); + ind = cur->pos; text = eina_ustrbuf_string_get(cur->node->unicode); - if (text[ind]) ind++; + + if (text[ind]) + { + if (per_cluster) + ind = _evas_textblock_cursor_cluster_pos_get(cur, EINA_TRUE); + + if (ind <= (int)cur->pos) + ind = cur->pos + 1; + } + /* Only allow pointing a null if it's the last paragraph. * because we don't have a PS there. */ if (text[ind]) @@ -9311,23 +9414,30 @@ evas_textblock_cursor_char_next(Efl_Text_Cursor_Cursor *cur) } } -EOLIAN static void -_efl_canvas_text_efl_text_cursor_cursor_char_next(Eo *eo_obj, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur) -{ - ASYNC_BLOCK; - evas_textblock_cursor_char_next(cur); - efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); -} - static Eina_Bool -_evas_textblock_cursor_char_prev(Efl_Text_Cursor_Cursor *cur) +_evas_textblock_cursor_prev(Evas_Textblock_Cursor *cur, Eina_Bool per_cluster) { + Evas_Object_Protected_Data *obj; if (!cur) return EINA_FALSE; TB_NULL_CHECK(cur->node, EINA_FALSE); + obj = efl_data_scope_get(cur->obj, EFL_CANVAS_OBJECT_CLASS); + evas_object_async_block(obj); + if (cur->pos != 0) { + if (per_cluster) + { + size_t ret = _evas_textblock_cursor_cluster_pos_get(cur, EINA_FALSE); + + if (ret != cur->pos) + { + cur->pos = ret; + return EINA_TRUE; + } + } + cur->pos--; return EINA_TRUE; } @@ -9335,18 +9445,59 @@ _evas_textblock_cursor_char_prev(Efl_Text_Cursor_Cursor *cur) } EAPI Eina_Bool +evas_textblock_cursor_char_next(Efl_Text_Cursor_Cursor *cur) +{ + return _evas_textblock_cursor_next(cur, EINA_FALSE); +} + +EOLIAN static void +_efl_canvas_text_efl_text_cursor_cursor_char_next(Eo *eo_obj, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur) +{ + ASYNC_BLOCK; + if (_evas_textblock_cursor_next(cur, EINA_FALSE)) + efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); +} + +EAPI Eina_Bool evas_textblock_cursor_char_prev(Efl_Text_Cursor_Cursor *cur) { - if (!cur) return EINA_FALSE; - return _evas_textblock_cursor_char_prev(cur); + return _evas_textblock_cursor_prev(cur, EINA_FALSE); } EOLIAN static void _efl_canvas_text_efl_text_cursor_cursor_char_prev(Eo *eo_obj EINA_UNUSED, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur) { ASYNC_BLOCK; - _evas_textblock_cursor_char_prev(cur); - efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); + if (_evas_textblock_cursor_prev(cur, EINA_FALSE)) + efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); +} + +EAPI Eina_Bool +evas_textblock_cursor_cluster_next(Efl_Text_Cursor_Cursor *cur) +{ + return _evas_textblock_cursor_next(cur, EINA_TRUE); +} + +EOLIAN static void +_efl_canvas_text_efl_text_cursor_cursor_cluster_next(Eo *eo_obj, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur) +{ + ASYNC_BLOCK; + if (_evas_textblock_cursor_next(cur, EINA_TRUE)) + efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); +} + +EAPI Eina_Bool +evas_textblock_cursor_cluster_prev(Efl_Text_Cursor_Cursor *cur) +{ + return _evas_textblock_cursor_prev(cur, EINA_TRUE); +} + +EOLIAN static void +_efl_canvas_text_efl_text_cursor_cursor_cluster_prev(Eo *eo_obj, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur) +{ + ASYNC_BLOCK; + if (_evas_textblock_cursor_prev(cur, EINA_TRUE)) + efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); } EAPI void @@ -12031,15 +12182,16 @@ _efl_canvas_text_visible_range_get(Eo *eo_obj EINA_UNUSED, return EINA_TRUE; } - -EAPI Eina_Bool -evas_textblock_cursor_char_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y) +static Eina_Bool +_evas_textblock_cursor_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y, Eina_Bool per_cluster) { Evas_Object_Textblock_Paragraph *found_par; Evas_Object_Textblock_Line *ln; Evas_Object_Textblock_Item *it = NULL; Eina_Bool ret = EINA_FALSE; + if (!cur) return ret; + Evas_Object_Protected_Data *obj = efl_data_scope_get(cur->obj, EFL_CANVAS_OBJECT_CLASS); evas_object_async_block(obj); Efl_Canvas_Text_Data *o = efl_data_scope_get(cur->obj, MY_CLASS); @@ -12112,6 +12264,63 @@ evas_textblock_cursor_char_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, &cx, &cy, &cw, &ch); if (pos < 0) goto end; + + if ((pos > 0) && per_cluster) + { + size_t len = eina_ustrbuf_length_get(it->text_node->unicode); + char *grapheme_breaks = _evas_textblock_grapheme_breaks_new(it, len); + + /* If current position is not breakable, + * try to move cursor to a nearest breakable position. */ + if (grapheme_breaks && (grapheme_breaks[pos + it->text_pos - 1] != GRAPHEMEBREAK_BREAK)) + { + size_t left_index = pos + it->text_pos - 1; + size_t right_index = pos + it->text_pos - 1; + int lx, rx; + + /* To the left */ + while ((left_index > 0) && + (grapheme_breaks[left_index] != GRAPHEMEBREAK_BREAK)) + { + left_index--; + } + + ENFN->font_pen_coords_get(ENC, + ti->parent.format->font.font, + &ti->text_props, + left_index - it->text_pos + 1, + &lx, NULL, NULL, NULL); + + /* To the right */ + while ((right_index < len) && + (grapheme_breaks[right_index] != GRAPHEMEBREAK_BREAK)) + { + right_index++; + } + + ENFN->font_pen_coords_get(ENC, + ti->parent.format->font.font, + &ti->text_props, + right_index - it->text_pos + 1, + &rx, NULL, NULL, NULL); + + /* Decide a nearest position by checking its geometry. */ + if (((ti->text_props.bidi_dir != EVAS_BIDI_DIRECTION_RTL) && + ((ln->x + it->x + rx - x) >= (x - (lx + ln->x + it->x)))) || + ((ti->text_props.bidi_dir == EVAS_BIDI_DIRECTION_RTL) && + ((ln->x + it->x + lx - x) >= (x - (rx + ln->x + it->x))))) + { + pos = left_index - it->text_pos + 1; + } + else + { + pos = right_index - it->text_pos + 1; + } + } + + free(grapheme_breaks); + } + cur->pos = pos + it->text_pos; cur->node = it->text_node; ret = EINA_TRUE; @@ -12167,6 +12376,18 @@ end: return ret; } +EAPI Eina_Bool +evas_textblock_cursor_char_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y) +{ + return _evas_textblock_cursor_coord_set(cur, x, y, EINA_FALSE); +} + +EAPI Eina_Bool +evas_textblock_cursor_cluster_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y) +{ + return _evas_textblock_cursor_coord_set(cur, x, y, EINA_TRUE); +} + EOLIAN static void _efl_canvas_text_efl_text_cursor_cursor_coord_set(Eo *eo_obj EINA_UNUSED, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur EINA_UNUSED, Evas_Coord x, Evas_Coord y) @@ -12175,6 +12396,14 @@ _efl_canvas_text_efl_text_cursor_cursor_coord_set(Eo *eo_obj EINA_UNUSED, Efl_Ca evas_textblock_cursor_char_coord_set(cur, x, y); } +EOLIAN static void +_efl_canvas_text_efl_text_cursor_cursor_cluster_coord_set(Eo *eo_obj EINA_UNUSED, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur EINA_UNUSED, + Evas_Coord x, Evas_Coord y) +{ + ASYNC_BLOCK; + evas_textblock_cursor_cluster_coord_set(cur, x, y); +} + EAPI int evas_textblock_cursor_line_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord y) { @@ -13279,6 +13508,7 @@ evas_object_textblock_init(Evas_Object *eo_obj) linebreak_init = EINA_TRUE; init_linebreak(); init_wordbreak(); + init_graphemebreak(); } o = obj->private_data; @@ -15194,7 +15424,7 @@ _efl_canvas_text_efl_text_annotate_range_annotations_get(const Eo *eo_obj, Efl_C if (!it->start_node || !it->end_node) continue; _textblock_cursor_pos_at_fnode_set(eo_obj, &start2, it->start_node); _textblock_cursor_pos_at_fnode_set(eo_obj, &end2, it->end_node); - _evas_textblock_cursor_char_prev(&end2); + evas_textblock_cursor_char_prev(&end2); if (!((evas_textblock_cursor_compare(&start2, end) > 0) || (evas_textblock_cursor_compare(&end2, start) < 0))) { |