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
This commit is contained in:
Thiep Ha 2014-11-26 11:29:08 +00:00 committed by Tom Hacohen
parent f6a5ada4a2
commit c30303d7e8
4 changed files with 430 additions and 37 deletions

View File

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

View File

@ -3980,6 +3980,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 ?
*

View File

@ -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)
{

View File

@ -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