diff --git a/src/lib/edje/edje_entry.c b/src/lib/edje/edje_entry.c index f2a97ccf90..5d7097f505 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) + 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; } } } diff --git a/src/lib/evas/Evas_Common.h b/src/lib/evas/Evas_Common.h index 94acba2163..a09cb20cc4 100644 --- a/src/lib/evas/Evas_Common.h +++ b/src/lib/evas/Evas_Common.h @@ -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 ? * diff --git a/src/lib/evas/canvas/evas_object_textblock.c b/src/lib/evas/canvas/evas_object_textblock.c index 9b9a9eea58..35e3da550c 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 1c071bdb02..55810e9962 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
is a test.
Another
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, "testtest2test3"); + + 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