tasn pushed a commit to branch master.

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

commit c30303d7e8dd92763298156d9bcaee2b1b1d5d86
Author: Thiep Ha <thie...@gmail.com>
Date:   Wed Nov 26 11:29:08 2014 +0000

    edje entry: improve selection performance
    
    Summary:
    The selection performance is slow if we select large chunk of text.
    This is caused by many rectangles created and deleted.
    This patch provides a way to improve it by combine selection rectangles
    of line in middle into one rectangles (i.e, if we have N lines,
    the selection rectangle for lines 2 to N-1 will be combined into one.)
    
    @feature
    
    Reviewers: raster, cedric, tasn
    
    Subscribers: herdsman, woohyun, cedric
    
    Differential Revision: https://phab.enlightenment.org/D1508
---
 src/lib/edje/edje_entry.c                   |  73 ++++++-----
 src/lib/evas/Evas_Common.h                  |  11 ++
 src/lib/evas/canvas/evas_object_textblock.c | 186 +++++++++++++++++++++++++-
 src/tests/evas/evas_test_textblock.c        | 197 ++++++++++++++++++++++++++++
 4 files changed, 430 insertions(+), 37 deletions(-)

diff --git a/src/lib/edje/edje_entry.c b/src/lib/edje/edje_entry.c
index f2a97cc..5d7097f 100644
--- a/src/lib/edje/edje_entry.c
+++ b/src/lib/edje/edje_entry.c
@@ -620,30 +620,29 @@ _sel_clear(Edje *ed, Evas_Textblock_Cursor *c 
EINA_UNUSED, Evas_Object *o EINA_U
 static void
 _sel_update(Edje *ed, Evas_Textblock_Cursor *c EINA_UNUSED, Evas_Object *o, 
Entry *en)
 {
-   Eina_List *range = NULL, *l;
-   Sel *sel;
-   Evas_Coord x, y, w, h;
+   Evas_Coord x, y;
    Evas_Object *smart, *clip;
 
    smart = evas_object_smart_parent_get(o);
    clip = evas_object_clip_get(o);
-   if (en->sel_start)
-     range = evas_textblock_cursor_range_geometry_get(en->sel_start, 
en->sel_end);
-   else
-     return;
-   if (eina_list_count(range) != eina_list_count(en->sel))
+   if (!en->sel_start)
+      return;
+
+   evas_object_geometry_get(o, &x, &y, NULL, NULL);
+   if (en->have_selection)
      {
-        while (en->sel)
-          {
-             sel = en->sel->data;
-             if (sel->obj_bg) evas_object_del(sel->obj_bg);
-             if (sel->obj_fg) evas_object_del(sel->obj_fg);
-             free(sel);
-             en->sel = eina_list_remove_list(en->sel, en->sel);
-          }
-        if (en->have_selection)
+        Eina_Iterator *range = NULL;
+        Eina_List *l;
+        Sel *sel;
+        Evas_Textblock_Rectangle *r;
+
+        range = evas_textblock_cursor_range_simple_geometry_get(en->sel_start,
+                                                                en->sel_end);
+
+        l = en->sel;
+        EINA_ITERATOR_FOREACH(range, r)
           {
-             for (l = range; l; l = eina_list_next(l))
+             if (!l)
                {
                   Evas_Object *ob;
 
@@ -669,17 +668,13 @@ _sel_update(Edje *ed, Evas_Textblock_Cursor *c 
EINA_UNUSED, Evas_Object *o, Entr
                   sel->obj_fg = ob;
                   _edje_subobj_register(ed, sel->obj_fg);
                }
-          }
-     }
-   x = y = w = h = -1;
-   evas_object_geometry_get(o, &x, &y, &w, &h);
-   if (en->have_selection)
-     {
-        EINA_LIST_FOREACH(en->sel, l, sel)
-          {
-             Evas_Textblock_Rectangle *r;
+             else
+               {
+                  sel = eina_list_data_get(l);
+                  l = l->next;
+               }
+             *(&(sel->rect)) = *r;
 
-             r = range->data;
              if (sel->obj_bg)
                {
                   evas_object_move(sel->obj_bg, x + r->x, y + r->y);
@@ -690,17 +685,23 @@ _sel_update(Edje *ed, Evas_Textblock_Cursor *c 
EINA_UNUSED, Evas_Object *o, Entr
                   evas_object_move(sel->obj_fg, x + r->x, y + r->y);
                   evas_object_resize(sel->obj_fg, r->w, r->h);
                }
-             *(&(sel->rect)) = *r;
-             range = eina_list_remove_list(range, range);
              free(r);
           }
-     }
-   else
-     {
-        while (range)
+        eina_iterator_free(range);
+
+        /* delete redundant selection rects */
+        while (l)
           {
-             free(range->data);
-             range = eina_list_remove_list(range, range);
+             Eina_List *temp = l->next;
+             sel = eina_list_data_get(l);
+             if (sel)
+               {
+                  if (sel->obj_bg) evas_object_del(sel->obj_bg);
+                  if (sel->obj_fg) evas_object_del(sel->obj_fg);
+                  free(sel);
+               }
+             en->sel = eina_list_remove_list(en->sel, l);
+             l = temp;
           }
      }
 }
diff --git a/src/lib/evas/Evas_Common.h b/src/lib/evas/Evas_Common.h
index 94acba2..a09cb20 100644
--- a/src/lib/evas/Evas_Common.h
+++ b/src/lib/evas/Evas_Common.h
@@ -3981,6 +3981,17 @@ EAPI int                                      
evas_textblock_cursor_line_coord_s
 EAPI Eina_List                               
*evas_textblock_cursor_range_geometry_get(const Evas_Textblock_Cursor *cur1, 
const Evas_Textblock_Cursor *cur2) EINA_WARN_UNUSED_RESULT EINA_ARG_NONNULL(1, 
2);
 
 /**
+ * Get the simple geometry of a range.
+ * The simple geometry is the geomtry in which rectangles in middle
+ * lines of range are merged into one big rectangle.
+ *
+ * @param cur1 one side of the range.
+ * @param cur2 other side of the range.
+ * @return an iterator of rectangles representing the geometry of the range.
+ */
+EAPI Eina_Iterator                               
*evas_textblock_cursor_range_simple_geometry_get(const Evas_Textblock_Cursor 
*cur1, const Evas_Textblock_Cursor *cur2) EINA_WARN_UNUSED_RESULT 
EINA_ARG_NONNULL(1, 2);
+
+/**
  * Get the geometry of ?
  *
  * @param cur one side of the range.
diff --git a/src/lib/evas/canvas/evas_object_textblock.c 
b/src/lib/evas/canvas/evas_object_textblock.c
index 9b9a9ee..35e3da5 100644
--- a/src/lib/evas/canvas/evas_object_textblock.c
+++ b/src/lib/evas/canvas/evas_object_textblock.c
@@ -199,7 +199,12 @@ typedef struct _Evas_Object_Textblock_Format_Item 
Evas_Object_Textblock_Format_I
  * A textblock format.
  */
 typedef struct _Evas_Object_Textblock_Format      Evas_Object_Textblock_Format;
-
+/**
+ * @internal
+ * @typedef Evas_Textblock_Selection_Iterator
+ * A textblock selection iterator.
+ */
+typedef struct _Evas_Textblock_Selection_Iterator 
Evas_Textblock_Selection_Iterator;
 /**
  * @internal
  * @def IS_AT_END(ti, ind)
@@ -513,6 +518,13 @@ struct _Evas_Object_Textblock
    Eina_Bool                           legacy_newline : 1;
 };
 
+struct _Evas_Textblock_Selection_Iterator
+{
+   Eina_Iterator                       iterator; /**< Eina Iterator. */
+   Eina_List                           *list; /**< Head of list. */
+   Eina_List                           *current; /**< Current node in loop. */
+};
+
 /* private methods for textblock objects */
 static void evas_object_textblock_init(Evas_Object *eo_obj);
 static void evas_object_textblock_render(Evas_Object *eo_obj,
@@ -597,6 +609,85 @@ static void 
_evas_textblock_invalidate_all(Evas_Textblock_Data *o);
 static void _evas_textblock_cursors_update_offset(const Evas_Textblock_Cursor 
*cur, const Evas_Object_Textblock_Node_Text *n, size_t start, int offset);
 static void _evas_textblock_cursors_set_node(Evas_Textblock_Data *o, const 
Evas_Object_Textblock_Node_Text *n, Evas_Object_Textblock_Node_Text *new_node);
 
+/** selection iterator */
+/**
+  * @internal
+  * Returns the value of the current data of list node,
+  * and goes to the next list node.
+  *
+  * @param it the iterator.
+  * @param data the data of the current list node.
+  * @return EINA_FALSE if the current list node does not exists.
+  * Otherwise, returns EINA_TRUE.
+  */
+static Eina_Bool
+_evas_textblock_selection_iterator_next(Evas_Textblock_Selection_Iterator *it, 
void **data)
+{
+   if (!it->current)
+     return EINA_FALSE;
+
+   *data = eina_list_data_get(it->current);
+   it->current = eina_list_next(it->current);
+
+   return EINA_TRUE;
+}
+
+/**
+  * @internal
+  * Gets the iterator container (Eina_List) which created the iterator.
+  * @param it the iterator.
+  * @return A pointer to Eina_List.
+  */
+static Eina_List *
+_evas_textblock_selection_iterator_get_container(Evas_Textblock_Selection_Iterator
 *it)
+{
+   return it->list;
+}
+
+/**
+  * @internal
+  * Frees the iterator container (Eina_List).
+  * @param it the iterator.
+  */
+static void
+_evas_textblock_selection_iterator_free(Evas_Textblock_Selection_Iterator *it)
+{
+   while (it->list)
+     it->list = eina_list_remove_list(it->list, it->list);
+   EINA_MAGIC_SET(&it->iterator, 0);
+   free(it);
+}
+
+/**
+  * @internal
+  * Creates newly allocated  iterator associated to a list.
+  * @param list The list.
+  * @return If the memory cannot be allocated, NULL is returned.
+  * Otherwise, a valid iterator is returned.
+  */
+Eina_Iterator *
+_evas_textblock_selection_iterator_new(Eina_List *list)
+{
+   Evas_Textblock_Selection_Iterator *it;
+
+   it = calloc(1, sizeof(Evas_Textblock_Selection_Iterator));
+   if (!it) return NULL;
+
+   EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR);
+   it->list = list;
+   it->current = list;
+
+   it->iterator.version = EINA_ITERATOR_VERSION;
+   it->iterator.next = FUNC_ITERATOR_NEXT(
+                                     _evas_textblock_selection_iterator_next);
+   it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER(
+                            _evas_textblock_selection_iterator_get_container);
+   it->iterator.free = FUNC_ITERATOR_FREE(
+                                     _evas_textblock_selection_iterator_free);
+
+   return &it->iterator;
+}
+
 /* styles */
 /**
  * @internal
@@ -10343,6 +10434,99 @@ _evas_textblock_cursor_range_in_line_geometry_get(
    return rects;
 }
 
+EAPI Eina_Iterator *
+evas_textblock_cursor_range_simple_geometry_get(const Evas_Textblock_Cursor 
*cur1, const Evas_Textblock_Cursor *cur2)
+{
+   Evas_Object_Textblock_Line *ln1, *ln2;
+   Evas_Object_Textblock_Item *it1, *it2;
+   Eina_List *rects = NULL;
+   Eina_Iterator *itr = NULL;
+
+   if (!cur1 || !cur1->node) return NULL;
+   if (!cur2 || !cur2->node) return NULL;
+   if (cur1->obj != cur2->obj) return NULL;
+   Evas_Textblock_Data *o = eo_data_scope_get(cur1->obj, MY_CLASS);
+
+   _relayout_if_needed(cur1->obj, o);
+
+   if (evas_textblock_cursor_compare(cur1, cur2) > 0)
+     {
+        const Evas_Textblock_Cursor *tc;
+
+        tc = cur1;
+        cur1 = cur2;
+        cur2 = tc;
+     }
+
+   ln1 = ln2 = NULL;
+   it1 = it2 = NULL;
+   _find_layout_item_match(cur1, &ln1, &it1);
+   if (!ln1 || !it1) return NULL;
+   _find_layout_item_match(cur2, &ln2, &it2);
+   if (!ln2 || !it2) return NULL;
+
+   if (ln1 == ln2)
+     {
+        rects = _evas_textblock_cursor_range_in_line_geometry_get(ln1, cur1, 
cur2);
+     }
+   else
+     {
+        int lm = 0, rm = 0;
+        Eina_List *rects2 = NULL;
+        Evas_Coord w;
+        Evas_Textblock_Cursor *tc;
+        Evas_Textblock_Rectangle *tr;
+
+        if (ln1->items)
+          {
+             Evas_Object_Textblock_Format *fm = ln1->items->format;
+             if (fm)
+               {
+                  lm = fm->margin.l;
+                  rm = fm->margin.r;
+               }
+          }
+
+        evas_object_geometry_get(cur1->obj, NULL, NULL, &w, NULL);
+        rects = _evas_textblock_cursor_range_in_line_geometry_get(ln1, cur1, 
NULL);
+
+        /* Extend selection rectangle in first line */
+        tc = evas_object_textblock_cursor_new(cur1->obj);
+        evas_textblock_cursor_copy(cur1, tc);
+        evas_textblock_cursor_line_char_last(tc);
+        tr = calloc(1, sizeof(Evas_Textblock_Rectangle));
+        evas_textblock_cursor_pen_geometry_get(tc, &tr->x, &tr->y, &tr->w, 
&tr->h);
+        if (ln1->par->direction == EVAS_BIDI_DIRECTION_RTL)
+          {
+             tr->w = tr->x + tr->w - rm;
+             tr->x = lm;
+          }
+        else
+          {
+             tr->w = w - tr->x - rm;
+          }
+        rects = eina_list_append(rects, tr);
+        evas_textblock_cursor_free(tc);
+
+        rects2 = _evas_textblock_cursor_range_in_line_geometry_get(ln2, NULL, 
cur2);
+
+        /* Add middle rect */
+        if ((ln1->par->y + ln1->y + ln1->h) != (ln2->par->y + ln2->y))
+          {
+             tr = calloc(1, sizeof(Evas_Textblock_Rectangle));
+             tr->x = lm;
+             tr->y = ln1->par->y + ln1->y + ln1->h;
+             tr->w = w - tr->x - rm;
+             tr->h = ln2->par->y + ln2->y - tr->y;
+             rects = eina_list_append(rects, tr);
+          }
+        rects = eina_list_merge(rects, rects2);
+     }
+   itr = _evas_textblock_selection_iterator_new(rects);
+
+   return itr;
+}
+
 EAPI Eina_List *
 evas_textblock_cursor_range_geometry_get(const Evas_Textblock_Cursor *cur1, 
const Evas_Textblock_Cursor *cur2)
 {
diff --git a/src/tests/evas/evas_test_textblock.c 
b/src/tests/evas/evas_test_textblock.c
index 1c071bd..55810e9 100644
--- a/src/tests/evas/evas_test_textblock.c
+++ b/src/tests/evas/evas_test_textblock.c
@@ -2134,6 +2134,203 @@ START_TEST(evas_textblock_geometries)
    EINA_LIST_FREE(rects, tr)
       free(tr);
 
+   /* Range simple geometry */
+   /* Single line range */
+   Eina_Iterator *it, *it2;
+   Evas_Textblock_Rectangle *tr3;
+   Evas_Coord w = 200;
+   evas_object_textblock_text_markup_set(tb, "This <br/> is a 
test.<br/>Another <br/>text.");
+   evas_object_resize(tb, w, w / 2);
+   evas_textblock_cursor_pos_set(cur, 0);
+   evas_textblock_cursor_pos_set(main_cur, 3);
+
+   it = evas_textblock_cursor_range_simple_geometry_get(cur, main_cur);
+   fail_if(!it);
+   rects = eina_iterator_container_get(it);
+   fail_if(!rects);
+   it2 = evas_textblock_cursor_range_simple_geometry_get(main_cur, cur);
+   fail_if(!it2);
+   rects2 = eina_iterator_container_get(it2);
+   fail_if(!rects2);
+
+   fail_if(eina_list_count(rects) != 1);
+   fail_if(eina_list_count(rects2) != 1);
+
+   tr = eina_list_data_get(rects);
+   fail_if((tr->h <= 0) || (tr->w <= 0));
+   tr2 = eina_list_data_get(rects2);
+   fail_if((tr->h <= 0) || (tr->w <= 0));
+
+   fail_if((tr->x != tr2->x) || (tr->y != tr2->y) || (tr->w != tr2->w) ||
+           (tr->h != tr2->h));
+
+   EINA_LIST_FREE(rects, tr)
+      free(tr);
+   EINA_LIST_FREE(rects2, tr2)
+      free(tr2);
+   eina_iterator_free(it);
+   eina_iterator_free(it2);
+
+   /* Multiple range */
+   evas_textblock_cursor_pos_set(cur, 0);
+   evas_textblock_cursor_pos_set(main_cur, 16);
+
+   it = evas_textblock_cursor_range_simple_geometry_get(cur, main_cur);
+   fail_if(!it);
+   rects = eina_iterator_container_get(it);
+   fail_if(!rects);
+   it2 = evas_textblock_cursor_range_simple_geometry_get(main_cur, cur);
+   fail_if(!it2);
+   rects2 = eina_iterator_container_get(it2);
+   fail_if(!rects2);
+
+   fail_if(eina_list_count(rects) != 3);
+   fail_if(eina_list_count(rects2) != 3);
+
+   tr = eina_list_data_get(rects);
+   fail_if((tr->h <= 0) || (tr->w <= 0));
+   tr2 = eina_list_data_get(rects2);
+   fail_if((tr2->h <= 0) || (tr2->w <= 0));
+
+   fail_if((tr->x != tr2->x) || (tr->y != tr2->y) || (tr->w != tr2->w) ||
+           (tr->h != tr2->h));
+
+   tr = eina_list_nth(rects, 1);
+   fail_if((tr->h <= 0) || (tr->w <= 0));
+   tr2 = eina_list_data_get(eina_list_next(rects2));
+   fail_if((tr2->h <= 0) || (tr2->w <= 0));
+
+   fail_if((tr->x != tr2->x) || (tr->y != tr2->y) || (tr->w != tr2->w) ||
+           (tr->h != tr2->h));
+
+   tr = eina_list_nth(rects, 2);
+   fail_if((tr->h <= 0) || (tr->w <= 0));
+   tr2 = eina_list_nth(rects2, 2);
+   fail_if((tr2->h <= 0) || (tr2->w <= 0));
+
+   fail_if((tr->x != tr2->x) || (tr->y != tr2->y) || (tr->w != tr2->w) ||
+           (tr->h != tr2->h));
+
+   /* Check that the second line is positioned below the first */
+   tr = eina_list_data_get(rects);
+   tr2 = eina_list_nth(rects, 1);
+   tr3 = eina_list_nth(rects, 2);
+   fail_if((tr->y >= tr3->y) || (tr2->y >= tr3->y));
+   fail_if(tr2->x + tr2->w != w);
+
+   /* Have middle rectangle */
+   evas_textblock_cursor_pos_set(cur, 0);
+   evas_textblock_cursor_pos_set(main_cur, 31);
+   it = evas_textblock_cursor_range_simple_geometry_get(cur, main_cur);
+   fail_if(!it);
+   rects = eina_iterator_container_get(it);
+   fail_if(!rects);
+   it2 = evas_textblock_cursor_range_simple_geometry_get(main_cur, cur);
+   fail_if(!it2);
+   rects2 = eina_iterator_container_get(it2);
+   fail_if(!rects2);
+
+   fail_if(eina_list_count(rects) != 4);
+   fail_if(eina_list_count(rects) != 4);
+
+   tr = eina_list_data_get(rects);
+   fail_if((tr->h <= 0) || (tr->w <= 0));
+   tr2 = eina_list_data_get(rects2);
+   fail_if((tr2->h <= 0) || (tr2->w <= 0));
+   fail_if((tr->x != tr2->x) || (tr->y != tr2->y) || (tr->w != tr2->w) ||
+      (tr->h != tr2->h));
+
+   tr = eina_list_nth(rects, 1);
+   fail_if((tr->h <= 0) || (tr->w <= 0));
+   tr2 = eina_list_nth(rects2, 1);
+   fail_if((tr2->h <= 0) || (tr2->w <= 0));
+   fail_if((tr->x != tr2->x) || (tr->y != tr2->y) || (tr->w != tr2->w) ||
+           (tr->h != tr2->h));
+
+   tr = eina_list_nth(rects, 2);
+   fail_if((tr->h <= 0) || (tr->w <= 0));
+   tr2 = eina_list_nth(rects2, 2);
+   fail_if((tr2->h <= 0) || (tr2->w <= 0));
+   fail_if((tr->x != tr2->x) || (tr->y != tr2->y) || (tr->w != tr2->w) ||
+           (tr->h != tr2->h));
+
+   tr = eina_list_nth(rects, 3);
+   fail_if((tr->h <= 0) || (tr->w <= 0));
+   tr2 = eina_list_nth(rects2, 3);
+   fail_if((tr2->h <= 0) || (tr2->w <= 0));
+   fail_if((tr->x != tr2->x) || (tr->y != tr2->y) || (tr->w != tr2->w) ||
+           (tr->h != tr2->h));
+
+   /* Check that the middle rectanlge is between first and last rectangles */
+   tr = eina_list_data_get(rects);
+   tr2 = eina_list_nth(rects, 2);
+   tr3 = eina_list_nth(rects, 3);
+   fail_if((tr2->y < tr->y + tr->h) || (tr2->y + tr2->h > tr3->y));
+
+   /* Check that the middle rectangle has proper width */
+   tr = eina_list_data_get(rects);
+   tr2 = eina_list_nth(rects, 1);
+   fail_if((tr->y != tr2->y) || (tr->h != tr2->h));
+   tr3 = eina_list_nth(rects, 2);
+   fail_if((tr2->x + tr2->w != w) || (tr2->x + tr2->w != tr3->x + tr3->w));
+   tr2 = eina_list_nth(rects, 2);
+   tr3 = eina_list_nth(rects, 3);
+   fail_if((tr2->x != tr3->x));
+
+   EINA_LIST_FREE(rects, tr)
+      free(tr);
+   EINA_LIST_FREE(rects2, tr2)
+      free(tr2);
+   eina_iterator_free(it);
+   eina_iterator_free(it2);
+
+   /* Same run different scripts */
+   evas_object_textblock_text_markup_set(tb, "עבריתenglishрусскийעברית");
+
+   evas_textblock_cursor_pos_set(cur, 3);
+   evas_textblock_cursor_pos_set(main_cur, 7);
+   it = evas_textblock_cursor_range_simple_geometry_get(cur, main_cur);
+   fail_if(!it);
+   rects = eina_iterator_container_get(it);
+   fail_if(!rects);
+
+   fail_if(eina_list_count(rects) != 2);
+
+   EINA_LIST_FREE(rects, tr)
+      free(tr);
+   eina_iterator_free(it);
+
+   /* Same run different styles */
+   evas_object_textblock_text_markup_set(tb, "test<b>test2</b>test3");
+
+   evas_textblock_cursor_pos_set(cur, 3);
+   evas_textblock_cursor_pos_set(main_cur, 11);
+   it = evas_textblock_cursor_range_simple_geometry_get(cur, main_cur);
+   fail_if(!it);
+   rects = eina_iterator_container_get(it);
+
+   fail_if(eina_list_count(rects) != 3);
+
+   EINA_LIST_FREE(rects, tr)
+      free(tr);
+   eina_iterator_free(it);
+
+   /* Bidi text with a few back and forth from bidi. */
+   evas_object_textblock_text_markup_set(tb, "נגכדגךלח eountoheunth 
ךלחגדךכלח");
+
+   evas_textblock_cursor_pos_set(cur, 0);
+   evas_textblock_cursor_pos_set(main_cur, 28);
+   it = evas_textblock_cursor_range_simple_geometry_get(cur, main_cur);
+   fail_if(!it);
+   rects = eina_iterator_container_get(it);
+   fail_if(!rects);
+
+   ck_assert_int_eq(eina_list_count(rects), 3);
+
+   EINA_LIST_FREE(rects, tr)
+      free(tr);
+   eina_iterator_free(it);
+
    END_TB_TEST();
 }
 END_TEST

-- 


Reply via email to