evas_textblock : fix text insertion & selection with ps in single line

Summary:
when we have text that contains <ps> (example "p1<ps>p2") 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 <Eina.h>
  #include <Elementary.h>
  #include <Efl_Ui.h>

  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, "p1<ps>p2");
  }

  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
This commit is contained in:
Ali Alzyod 2020-04-16 20:03:31 +09:00 committed by WooHyun Jung
parent d62d380690
commit da36015148
2 changed files with 360 additions and 2 deletions

View File

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

View File

@ -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, "p1<ps/>p2<ps/>p3");
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, "p1<ps>p<b>2</b>2<ps>p3");
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, "p1<ps>p<b>2</b>2<ps>p3<ps>");
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);
}