evas_textblock : fix text insertion & selection with ps in single line
authorAli <ali198724@gmail.com>
Thu, 16 Apr 2020 11:03:31 +0000 (20:03 +0900)
committerJongmin Lee <jm105.lee@samsung.com>
Thu, 16 Apr 2020 21:31:35 +0000 (06:31 +0900)
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

src/lib/evas/canvas/evas_object_textblock.c
src/tests/elementary/efl_ui_test_text.c

index 3298169..c112475 100644 (file)
@@ -11411,7 +11411,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
                }
 
           }
@@ -12388,7 +12388,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
           {
@@ -17732,12 +17733,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);
 }
 
index d6c7a87..3eb9909 100644 (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);
 }