From da36015148b01331333acb52a584f9002cf28aeb Mon Sep 17 00:00:00 2001 From: Ali Date: Thu, 16 Apr 2020 20:03:31 +0900 Subject: [PATCH] evas_textblock : fix text insertion & selection with ps in single line Summary: when we have text that contains (example "p1p2") in a single line mode and the cursor position is after the ps tag then we try to insert any character using the keyboard it will show segmentation fault. also with the same text if we try to select the text we will notice that it is corrupted. this should resolve https://phab.enlightenment.org/T8594 Test Plan: #define EFL_EO_API_SUPPORT 1 #define EFL_BETA_API_SUPPORT 1 #include #include #include static void _gui_quit_cb(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED) { efl_exit(0); } static void _gui_setup() { Eo *win, *box; win = efl_add(EFL_UI_WIN_CLASS, efl_main_loop_get(), efl_ui_win_type_set(efl_added, EFL_UI_WIN_TYPE_BASIC), efl_text_set(efl_added, "Hello World"), efl_ui_win_autodel_set(efl_added, EINA_TRUE)); // when the user clicks "close" on a window there is a request to delete efl_event_callback_add(win, EFL_UI_WIN_EVENT_DELETE_REQUEST, _gui_quit_cb, NULL); box = efl_add(EFL_UI_BOX_CLASS, win, efl_content_set(win, efl_added), efl_gfx_hint_size_min_set(efl_added, EINA_SIZE2D(360, 240))); Eo *text = efl_add(EFL_UI_TEXTBOX_CLASS, box, efl_gfx_hint_weight_set(efl_added, 1.0, 1.0), efl_gfx_hint_align_set(efl_added, 1.0, 1.0), efl_pack(box, efl_added)); efl_text_interactive_selection_allowed_set(text, EINA_TRUE); efl_text_multiline_set(text,EINA_FALSE); efl_text_markup_set(text, "p1p2"); } EAPI_MAIN void efl_main(void *data EINA_UNUSED, const Efl_Event *ev EINA_UNUSED) { _gui_setup(); } EFL_MAIN() Reviewers: ali.alzyod, woohyun, zmike, bu5hm4n, segfaultxavi, stefan_schmidt Reviewed By: ali.alzyod, woohyun Subscribers: cedric, #reviewers, #committers Tags: #efl Differential Revision: https://phab.enlightenment.org/D11621 --- src/lib/evas/canvas/evas_object_textblock.c | 199 +++++++++++++++++++- src/tests/elementary/efl_ui_test_text.c | 163 ++++++++++++++++ 2 files changed, 360 insertions(+), 2 deletions(-) diff --git a/src/lib/evas/canvas/evas_object_textblock.c b/src/lib/evas/canvas/evas_object_textblock.c index 914a6cf998..d421acd07f 100644 --- a/src/lib/evas/canvas/evas_object_textblock.c +++ b/src/lib/evas/canvas/evas_object_textblock.c @@ -11101,7 +11101,7 @@ _evas_textblock_node_text_adjust_offsets_to_start(Efl_Canvas_Textblock_Data *o, if (_IS_PARAGRAPH_SEPARATOR(o, last_node->format)) { _evas_textblock_node_format_remove(o, last_node, 0); - return EINA_TRUE; + return o->multiline;//if single nothing to merge } } @@ -12074,7 +12074,8 @@ _evas_textblock_cursor_format_append(Efl_Text_Cursor_Handle *cur, _evas_textblock_cursors_update_offset(cur, cur->node, cur->pos, 1); if (_IS_PARAGRAPH_SEPARATOR(o, format)) { - _evas_textblock_cursor_break_paragraph(cur, n, EINA_TRUE); + if (o->multiline) + _evas_textblock_cursor_break_paragraph(cur, n, EINA_TRUE); } else { @@ -17006,12 +17007,206 @@ _efl_canvas_textblock_efl_text_format_wrap_get(const Eo *obj EINA_UNUSED, Efl_Ca return _FMT_INFO(wrap); } +void +clean_cursors_at_node(Eina_List **all_cursors, + Evas_Object_Textblock_Node_Text *tn, + Evas_Object_Textblock_Node_Text *main_node, + unsigned int len) +{ + Eina_List *l, *ll; + Efl_Text_Cursor_Handle *itr_cursor; + EINA_LIST_FOREACH_SAFE (*all_cursors, l, ll, itr_cursor) + { + if (tn == itr_cursor->node) + { + itr_cursor->pos += len; + itr_cursor->node = main_node; + itr_cursor->changed = EINA_TRUE; + *all_cursors = eina_list_remove(*all_cursors, itr_cursor); + } + } +} + +/** + * @internal + * Combine all text nodes in a single node, for convert from multi-line to single-line. + * @param obj The evas object, must not be NULL. + * @return EINA_TRUE if text nodes merged, else return EINA_FALSE + */ +static void +_merge_to_first_text_nodes(const Evas_Object *eo_obj) +{ + Efl_Canvas_Textblock_Data *o = efl_data_scope_get(eo_obj, MY_CLASS); + Evas_Object_Textblock_Node_Text *main_node, *tn; + Evas_Object_Textblock_Node_Format *fn; + int len, temp_len; + + if (!o->text_nodes || !(EINA_INLIST_GET(o->text_nodes)->next)) + return; + + main_node = o->text_nodes; + main_node->dirty = EINA_TRUE; + len = (int) eina_ustrbuf_length_get(main_node->unicode); + + Eina_List *all_cursors; + all_cursors = eina_list_clone(o->cursors); + all_cursors = eina_list_append(all_cursors, o->cursor); + + while ((tn = _NODE_TEXT(EINA_INLIST_GET(o->text_nodes)->next))) + { + fn = tn->format_node; + + if (fn && (fn->text_node == tn)) + { + fn->offset++; //add prev ps + } + + while (fn && (fn->text_node == tn)) + { + fn->text_node = main_node; + fn->format_change = EINA_TRUE; + + fn = _NODE_FORMAT(EINA_INLIST_GET(fn)->next); + } + + temp_len = (int) eina_ustrbuf_length_get(tn->unicode); + eina_ustrbuf_append_length(main_node->unicode, eina_ustrbuf_string_get(tn->unicode), temp_len); + + clean_cursors_at_node(&all_cursors, tn, main_node, len); + + len += temp_len; + + o->text_nodes = _NODE_TEXT(eina_inlist_remove( + EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(tn))); + _evas_textblock_node_text_free(tn); + } + + eina_list_free(all_cursors); +} + +void +clean_cursors_in_range(Eina_List **all_cursors, Evas_Object_Textblock_Node_Text *tn, unsigned int start, unsigned int end) +{ + Eina_List *l, *ll; + Efl_Text_Cursor_Handle *itr_cursor; + EINA_LIST_FOREACH_SAFE (*all_cursors, l, ll, itr_cursor) + { + if (itr_cursor->pos >= start && itr_cursor->pos <= end) + { + itr_cursor->pos -= start; + itr_cursor->node = tn; + itr_cursor->changed = EINA_TRUE; + *all_cursors = eina_list_remove(*all_cursors, itr_cursor); + } + } +} + +/** + * @internal + * split text node into multiple text nodes based on ps, called for convert from singleline to multiline. + * @param obj The evas object, must not be NULL. + * @return EINA_TRUE if text nodes splitted, else return EINA_FALSE + */ +static void +_split_text_nodes(const Evas_Object *eo_obj) +{ + Efl_Canvas_Textblock_Data *o = efl_data_scope_get(eo_obj, MY_CLASS); + Evas_Object_Textblock_Node_Text *tn; + Evas_Object_Textblock_Node_Format *fn; + Eina_Unicode *all_unicode; + Eina_List *all_cursors; + unsigned int len = 0, str_start = 0; + + if (!o->text_nodes || !o->format_nodes) + return; + + tn = o->text_nodes; + fn = tn->format_node; + + while (fn && !_IS_PARAGRAPH_SEPARATOR_SIMPLE(fn->format)) + { + len += fn->offset; + fn = _NODE_FORMAT(EINA_INLIST_GET(fn)->next); + } + + if(!fn) return; + + tn->dirty = EINA_TRUE; + len += fn->offset + 1; + all_unicode = eina_ustrbuf_string_steal(tn->unicode); + + eina_ustrbuf_append_n(tn->unicode, all_unicode, len); + + str_start += len; + + tn = _evas_textblock_node_text_new(); + o->text_nodes = _NODE_TEXT(eina_inlist_append( + EINA_INLIST_GET(o->text_nodes), + EINA_INLIST_GET(tn))); + + fn = _NODE_FORMAT(EINA_INLIST_GET(fn)->next); + + all_cursors = eina_list_clone(o->cursors); + all_cursors = eina_list_append(all_cursors, o->cursor); + + while (fn) + { + len = 0; + tn->format_node = fn; + tn->format_node->offset--; + + while (fn && !_IS_PARAGRAPH_SEPARATOR_SIMPLE(fn->format)) + { + len += fn->offset; + fn->text_node = tn; + fn->format_change = EINA_TRUE; + fn = _NODE_FORMAT(EINA_INLIST_GET(fn)->next); + } + + if (!fn) break; + + fn->text_node = tn; + fn->format_change = EINA_TRUE; + len += fn->offset + 1; + eina_ustrbuf_append_n(tn->unicode, all_unicode + str_start, len); + + clean_cursors_in_range(&all_cursors, tn, str_start, str_start + len); + + str_start += len; + + tn = _evas_textblock_node_text_new(); + o->text_nodes = _NODE_TEXT(eina_inlist_append( + EINA_INLIST_GET(o->text_nodes), + EINA_INLIST_GET(tn))); + + fn = _NODE_FORMAT(EINA_INLIST_GET(fn)->next); + } + + if (!tn->format_node) + tn->format_node = _NODE_FORMAT(EINA_INLIST_GET(o->format_nodes)->last); + + len = eina_unicode_strlen(all_unicode + str_start); + eina_ustrbuf_append_n(tn->unicode, all_unicode + str_start, len); + + clean_cursors_in_range(&all_cursors, tn, str_start, str_start + len); + eina_list_free(all_cursors); + + free(all_unicode); +} + static void _efl_canvas_textblock_efl_text_format_multiline_set(Eo *obj EINA_UNUSED, Efl_Canvas_Textblock_Data *o EINA_UNUSED, Eina_Bool enabled EINA_UNUSED) { ASYNC_BLOCK; + if (o->multiline == enabled) return; o->multiline = enabled; + + if (!o->multiline) + _merge_to_first_text_nodes(obj); + else + _split_text_nodes(obj); + _canvas_text_format_changed(obj, o); } diff --git a/src/tests/elementary/efl_ui_test_text.c b/src/tests/elementary/efl_ui_test_text.c index d6c7a87e72..3eb9909d53 100644 --- a/src/tests/elementary/efl_ui_test_text.c +++ b/src/tests/elementary/efl_ui_test_text.c @@ -300,6 +300,166 @@ EFL_START_TEST(text_editable) } EFL_END_TEST +EFL_START_TEST(text_multiline_selection) +{ + Eo *txt, *win; + Eo *cursor1, *cursor2; + Eina_Rect rc1, rc2; + win = win_add(); + txt = efl_add(EFL_UI_TEXTBOX_CLASS, win); + efl_text_markup_set(txt, "p1p2p3"); + efl_text_multiline_set(txt, EINA_FALSE); + ecore_main_loop_iterate(); + efl_text_interactive_all_select(txt); + efl_text_interactive_selection_cursors_get(txt, &cursor1, &cursor2); + rc1 = efl_text_cursor_object_cursor_geometry_get(cursor1, EFL_TEXT_CURSOR_TYPE_BEFORE); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor2, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_eq(rc1.y, rc2.y); + ck_assert_int_ne(rc1.x, rc2.x); + + efl_del(txt); + efl_del(win); +} +EFL_END_TEST + +EFL_START_TEST(text_singleline_cursor_movement) +{ + Eo *txt, *win; + Eo *cursor; + Eina_Rect rc1, rc2; + win = win_add(); + txt = efl_add(EFL_UI_TEXTBOX_CLASS, win); + efl_text_markup_set(txt, "p1p22p3"); + efl_text_multiline_set(txt, EINA_FALSE); + ecore_main_loop_iterate(); + + cursor = efl_text_interactive_main_cursor_get(txt); + efl_text_cursor_object_move(cursor, EFL_TEXT_CURSOR_MOVE_TYPE_FIRST); + ck_assert_int_eq(efl_text_cursor_object_position_get(cursor), 0); + rc1 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + + efl_text_cursor_object_move(cursor, EFL_TEXT_CURSOR_MOVE_TYPE_LAST); + ck_assert_int_eq(efl_text_cursor_object_position_get(cursor), 9); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_eq(rc1.y, rc2.y); + ck_assert_int_ne(rc1.x, rc2.x); + + efl_text_cursor_object_move(cursor, EFL_TEXT_CURSOR_MOVE_TYPE_LINE_START); + ck_assert_int_eq(efl_text_cursor_object_position_get(cursor), 0); + rc1 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + + efl_text_cursor_object_move(cursor, EFL_TEXT_CURSOR_MOVE_TYPE_LINE_END); + ck_assert_int_eq(efl_text_cursor_object_position_get(cursor), 9); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_eq(rc1.y, rc2.y); + ck_assert_int_ne(rc1.x, rc2.x); + + efl_text_cursor_object_move(cursor, EFL_TEXT_CURSOR_MOVE_TYPE_PARAGRAPH_NEXT); + ck_assert_int_eq(efl_text_cursor_object_position_get(cursor), 9); //do not move + rc1 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + + efl_text_cursor_object_move(cursor, EFL_TEXT_CURSOR_MOVE_TYPE_PARAGRAPH_PREVIOUS); + ck_assert_int_eq(efl_text_cursor_object_position_get(cursor), 9); //do not move + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_eq(rc1.y, rc2.y); + ck_assert_int_eq(rc1.x, rc2.x); + + efl_text_cursor_object_move(cursor, EFL_TEXT_CURSOR_MOVE_TYPE_PARAGRAPH_START); + ck_assert_int_eq(efl_text_cursor_object_position_get(cursor), 0); + rc1 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + + efl_text_cursor_object_move(cursor, EFL_TEXT_CURSOR_MOVE_TYPE_PARAGRAPH_END); + ck_assert_int_eq(efl_text_cursor_object_position_get(cursor), 9); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_eq(rc1.y, rc2.y); + ck_assert_int_ne(rc1.x, rc2.x); + + efl_del(txt); + efl_del(win); +} +EFL_END_TEST + +EFL_START_TEST(text_multiline_singleline_cursor_pos) +{ + Eo *txt, *win; + Eo *cursor, *cursor1, *cursor2; + Eina_Rect rc1, rc2; + win = win_add(); + txt = efl_add(EFL_UI_TEXTBOX_CLASS, win); + efl_text_markup_set(txt, "p1p22p3"); + cursor = efl_text_interactive_main_cursor_get(txt); + cursor1 = efl_ui_textbox_cursor_create(txt); + efl_text_cursor_object_position_set(cursor1, 4); + cursor2 = efl_ui_textbox_cursor_create(txt); + efl_text_cursor_object_position_set(cursor2, 8); + + efl_text_multiline_set(txt, EINA_FALSE); + ck_assert_uint_eq(efl_text_cursor_object_content_get(cursor1), '2'); + ck_assert_uint_eq(efl_text_cursor_object_content_get(cursor2), '3'); + + efl_text_cursor_object_position_set(cursor, 0); + rc1 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + efl_text_multiline_set(txt, EINA_TRUE); + ck_assert_uint_eq(efl_text_cursor_object_content_get(cursor1), '2'); + ck_assert_uint_eq(efl_text_cursor_object_content_get(cursor2), '3'); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_eq(rc1.y, rc2.y); + ck_assert_int_eq(rc1.x, rc2.x); + + efl_text_multiline_set(txt, EINA_FALSE); + efl_text_cursor_object_position_set(cursor, 2); + rc1 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + efl_text_multiline_set(txt, EINA_TRUE); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_eq(rc1.y, rc2.y); + ck_assert_int_eq(rc1.x, rc2.x); + efl_text_multiline_set(txt, EINA_FALSE); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_eq(rc1.y, rc2.y); + ck_assert_int_eq(rc1.x, rc2.x); + + efl_text_multiline_set(txt, EINA_FALSE); + efl_text_cursor_object_position_set(cursor, 3); + rc1 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + efl_text_multiline_set(txt, EINA_TRUE); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_ne(rc1.y, rc2.y); + ck_assert_int_ne(rc1.x, rc2.x); + efl_text_multiline_set(txt, EINA_FALSE); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_eq(rc1.y, rc2.y); + ck_assert_int_eq(rc1.x, rc2.x); + + efl_text_multiline_set(txt, EINA_FALSE); + efl_text_cursor_object_position_set(cursor, 4); + rc1 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + efl_text_multiline_set(txt, EINA_TRUE); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_ne(rc1.y, rc2.y); + ck_assert_int_ne(rc1.x, rc2.x); + efl_text_multiline_set(txt, EINA_FALSE); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_eq(rc1.y, rc2.y); + ck_assert_int_eq(rc1.x, rc2.x); + + efl_text_multiline_set(txt, EINA_FALSE); + efl_text_cursor_object_position_set(cursor, 10); + rc1 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + efl_text_multiline_set(txt, EINA_TRUE); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_ne(rc1.y, rc2.y); + ck_assert_int_ne(rc1.x, rc2.x); + efl_text_multiline_set(txt, EINA_FALSE); + rc2 = efl_text_cursor_object_cursor_geometry_get(cursor, EFL_TEXT_CURSOR_TYPE_BEFORE); + ck_assert_int_eq(rc1.y, rc2.y); + ck_assert_int_eq(rc1.x, rc2.x); + + + efl_del(txt); + efl_del(win); +} +EFL_END_TEST + void efl_ui_test_text(TCase *tc) { tcase_add_test(tc, text_cnp); @@ -310,4 +470,7 @@ void efl_ui_test_text(TCase *tc) tcase_add_test(tc, text_change_event); tcase_add_test(tc, text_keys_handler); tcase_add_test(tc, text_editable); + tcase_add_test(tc, text_multiline_selection); + tcase_add_test(tc, text_singleline_cursor_movement); + tcase_add_test(tc, text_multiline_singleline_cursor_pos); }