woohyun pushed a commit to branch master.

http://git.enlightenment.org/core/efl.git/commit/?id=da36015148b01331333acb52a584f9002cf28aeb

commit da36015148b01331333acb52a584f9002cf28aeb
Author: Ali <[email protected]>
Date:   Thu Apr 16 20:03:31 2020 +0900

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

-- 


Reply via email to