From 43edd17f41308ba3bf42b38f727aad76db87aa72 Mon Sep 17 00:00:00 2001 From: Marcel Hollerbach Date: Tue, 20 Aug 2019 18:22:00 +0200 Subject: [PATCH] efl_ui_single_selectable: add properties and events this commit adds 2 new features, a new event and a new property The fallback property is usefull if you want to have a selection that always falls back to some preconfigured things. As a usecase, think about a list of languges where you can select the languages you want to have. When everything is deselected, the list will simply fallback to the preconfigured element (for example the language configured before) The event is annoncing that there was a change to the selection. The event is allowed to be defered, it will be emitted once for a range of selection elements that happened during one loop iteration. This is usefull if you are interested in a general selection where you want to know that we changed from a specific set of selected element to another set of elements, without monitoring every selectable in the widget. ref T8057 Reviewed-by: Mike Blumenkrantz Reviewed-by: Cedric BAIL Differential Revision: https://phab.enlightenment.org/D9662 --- src/lib/elementary/efl_ui_collection.c | 63 ++++++++- src/lib/elementary/efl_ui_collection.eo | 2 +- .../elementary/efl_ui_single_selectable.eo | 15 +++ .../spec/efl_test_multi_selectable.c | 126 ++++++++---------- .../spec/efl_test_single_selectable.c | 25 ++++ 5 files changed, 157 insertions(+), 74 deletions(-) diff --git a/src/lib/elementary/efl_ui_collection.c b/src/lib/elementary/efl_ui_collection.c index 0217d89e5d..210dcd9476 100644 --- a/src/lib/elementary/efl_ui_collection.c +++ b/src/lib/elementary/efl_ui_collection.c @@ -132,10 +132,12 @@ typedef struct { Efl_Ui_Pan *pan; Eina_List *selected; Eina_List *items; + Efl_Ui_Selection *fallback; Efl_Ui_Select_Mode mode; Efl_Ui_Layout_Orientation dir; Eina_Size2D content_min_size; Efl_Ui_Position_Manager_Entity *pos_man; + Eina_Future *selection_changed_job; struct { Eina_Bool w; Eina_Bool h; @@ -418,6 +420,8 @@ _efl_ui_collection_efl_object_invalidate(Eo *obj, Efl_Ui_Collection_Data *pd EIN { efl_ui_collection_position_manager_set(obj, NULL); + efl_ui_single_selectable_fallback_selection_set(obj, NULL); + deselect_all(pd); while(pd->items) @@ -492,12 +496,51 @@ _efl_ui_collection_efl_ui_multi_selectable_select_mode_get(const Eo *obj EINA_UN return pd->mode; } +static Eina_Value +_schedule_selection_job_cb(Eo *o, void *data EINA_UNUSED, const Eina_Value value EINA_UNUSED) +{ + MY_DATA_GET(o, pd); + + pd->selection_changed_job = NULL; + + efl_event_callback_call(o, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, NULL); + + return EINA_VALUE_EMPTY; +} + +static void +_schedule_selection_changed(Eo *obj, Efl_Ui_Collection_Data *pd) +{ + Eina_Future *f; + + if (pd->selection_changed_job) return; + + f = efl_loop_job(efl_main_loop_get()); + pd->selection_changed_job = efl_future_then(obj, f, _schedule_selection_job_cb); + +} + +static void +_apply_fallback(Eo *obj EINA_UNUSED, Efl_Ui_Collection_Data *pd) +{ + if (pd->fallback && !pd->selected) + { + efl_ui_selectable_selected_set(pd->fallback, EINA_TRUE); + } +} + static void _selection_changed(void *data, const Efl_Event *ev) { Eina_Bool selection = *((Eina_Bool*) ev->info); Eo *obj = data; MY_DATA_GET(obj, pd); + Efl_Ui_Selection *fallback; + + //if this is the highest call in the tree of selection changes, safe the fallback and apply it later + //this way we ensure that we are not accidently adding fallback even if we just want to have a empty selection list + fallback = pd->fallback; + pd->fallback = NULL; if (selection) { @@ -520,13 +563,15 @@ _selection_changed(void *data, const Efl_Event *ev) return; } pd->selected = eina_list_append(pd->selected, ev->object); - efl_event_callback_call(obj, EFL_UI_EVENT_ITEM_SELECTED, ev->object); } else { pd->selected = eina_list_remove(pd->selected, ev->object); - efl_event_callback_call(obj, EFL_UI_EVENT_ITEM_UNSELECTED, ev->object); } + + pd->fallback = fallback; + _apply_fallback(obj, pd); + _schedule_selection_changed(obj, pd); } static void @@ -1010,6 +1055,19 @@ _efl_ui_collection_efl_ui_multi_selectable_unselect_range(Eo *obj, Efl_Ui_Collec _range_selection_find(obj, pd, a, b, EINA_FALSE); } +EOLIAN static void +_efl_ui_collection_efl_ui_single_selectable_fallback_selection_set(Eo *obj EINA_UNUSED, Efl_Ui_Collection_Data *pd, Efl_Ui_Selectable *fallback) +{ + pd->fallback = fallback; + _apply_fallback(obj, pd); +} + +EOLIAN static Efl_Ui_Selectable* +_efl_ui_collection_efl_ui_single_selectable_fallback_selection_get(const Eo *obj EINA_UNUSED, Efl_Ui_Collection_Data *pd) +{ + return pd->fallback; +} + #include "efl_ui_collection.eo.c" #define ITEM_IS_OUTSIDE_VISIBLE(id) id < collection_pd->start_id || id > collection_pd->end_id @@ -1090,4 +1148,5 @@ _efl_ui_collection_focus_manager_efl_ui_focus_manager_manager_focus_set(Eo *obj, efl_ui_focus_manager_focus_set(efl_super(obj, EFL_UI_COLLECTION_FOCUS_MANAGER_CLASS), focus); } + #include "efl_ui_collection_focus_manager.eo.c" diff --git a/src/lib/elementary/efl_ui_collection.eo b/src/lib/elementary/efl_ui_collection.eo index 76c7fac4eb..d566ff15e0 100644 --- a/src/lib/elementary/efl_ui_collection.eo +++ b/src/lib/elementary/efl_ui_collection.eo @@ -3,7 +3,6 @@ class @beta Efl.Ui.Collection extends Efl.Ui.Layout_Base implements Efl.Ui.Scrollbar, Efl.Pack_Linear, Efl.Pack_Layout, Efl.Ui.Layout_Orientable, - Efl.Ui.Container_Selectable, Efl.Ui.Multi_Selectable, Efl.Ui.Focus.Manager_Sub, Efl.Ui.Widget_Focus_Manager @@ -71,6 +70,7 @@ class @beta Efl.Ui.Collection extends Efl.Ui.Layout_Base implements Efl.Ui.Multi_Selectable.unselect_all; Efl.Ui.Multi_Selectable.select_range; Efl.Ui.Multi_Selectable.unselect_range; + Efl.Ui.Single_Selectable.fallback_selection {get; set;} } events { item,pressed : Efl.Ui.Item; [[A $press event occurred over an item.]] diff --git a/src/lib/elementary/efl_ui_single_selectable.eo b/src/lib/elementary/efl_ui_single_selectable.eo index c2777ff106..198ac10193 100644 --- a/src/lib/elementary/efl_ui_single_selectable.eo +++ b/src/lib/elementary/efl_ui_single_selectable.eo @@ -13,5 +13,20 @@ interface @beta Efl.Ui.Single_Selectable { selectable : Efl.Ui.Selectable; [[The latest selected item.]] } } + @property fallback_selection { + [[A object that will be selected in case nothing is selected + + A object set to this property will be selected instead of no item beeing selected. Which means, there will be always at least one element selected. + If this property is NULL, the state of "no item is selected" can be reached. + + Setting this property as a result of selection events results in undefined behavior. + ]] + values { + fallback : Efl.Ui.Selectable; + } + } + } + events { + selection_changed : void; [[Called when there is a change in the selection state, this event will collect all the item selection change events that are happening within one loop iteration. This means, you will only get this event once, even if a lot of items have changed. If you are interested in detailed changes, subscribe to the selection,changed event of Efl.Ui.Selectable.]] } } diff --git a/src/tests/elementary/spec/efl_test_multi_selectable.c b/src/tests/elementary/spec/efl_test_multi_selectable.c index 5d5c1ed824..da36e5b8f2 100644 --- a/src/tests/elementary/spec/efl_test_multi_selectable.c +++ b/src/tests/elementary/spec/efl_test_multi_selectable.c @@ -42,23 +42,22 @@ _iterator_to_array(Eina_Array **arr, Eina_Iterator *iter) EFL_START_TEST(test_multi_select) { - Efl_Ui_Item *selected = NULL; - Efl_Ui_Item *unselected = NULL; + int c = 0; + Eina_Array *arr_selected; efl_ui_select_mode_set(widget, EFL_UI_SELECT_MODE_MULTI); - efl_event_callback_add(widget, EFL_UI_EVENT_ITEM_SELECTED, _set_pointer_quit, &selected); - efl_event_callback_add(widget, EFL_UI_EVENT_ITEM_UNSELECTED, _set_pointer_quit, &unselected); + efl_event_callback_add(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, (Efl_Event_Cb) event_callback_single_call_int_data, &c); + efl_event_callback_add(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, event_callback_that_quits_the_main_loop_when_called, NULL); efl_ui_selectable_selected_set(efl_pack_content_get(widget, 0), EINA_TRUE); - ck_assert_ptr_eq(selected, efl_pack_content_get(widget, 0)); - ck_assert_ptr_eq(unselected, NULL); - selected = NULL; - unselected = NULL; + if (c == 0) efl_loop_begin(efl_main_loop_get()); + ck_assert_int_eq(c, 1); + c = 0; + efl_ui_selectable_selected_set(efl_pack_content_get(widget, 2), EINA_TRUE); - ck_assert_ptr_eq(selected, efl_pack_content_get(widget, 2)); - ck_assert_ptr_eq(unselected, NULL); - selected = NULL; - unselected = NULL; + if (c == 0) efl_loop_begin(efl_main_loop_get()); + ck_assert_int_eq(c, 1); + c = 0; ck_assert_int_eq(efl_ui_selectable_selected_get(efl_pack_content_get(widget, 0)), EINA_TRUE); ck_assert_int_eq(efl_ui_selectable_selected_get(efl_pack_content_get(widget, 2)), EINA_TRUE); ck_assert_ptr_eq(efl_ui_single_selectable_last_selected_get(widget), efl_pack_content_get(widget, 2)); @@ -68,68 +67,59 @@ EFL_START_TEST(test_multi_select) ck_assert_ptr_eq(eina_array_data_get(arr_selected, 1), efl_pack_content_get(widget, 2)); efl_ui_selectable_selected_set(efl_pack_content_get(widget, 2), EINA_TRUE); - ck_assert_ptr_eq(selected, NULL); - ck_assert_ptr_eq(unselected, NULL); - selected = NULL; - unselected = NULL; - efl_event_callback_del(widget, EFL_UI_EVENT_ITEM_SELECTED, _set_pointer_quit, &selected); - efl_event_callback_del(widget, EFL_UI_EVENT_ITEM_UNSELECTED, _set_pointer_quit, &unselected); + ck_assert_int_eq(c, 0); + + efl_event_callback_del(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, (Efl_Event_Cb) event_callback_single_call_int_data, &c); + efl_event_callback_del(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, event_callback_that_quits_the_main_loop_when_called, NULL); } EFL_END_TEST EFL_START_TEST(test_multi_select_removal) { - Efl_Ui_Item *selected = NULL; - Efl_Ui_Item *unselected = NULL; + int c = 0; Eina_Array *arr_selected; efl_ui_select_mode_set(widget, EFL_UI_SELECT_MODE_MULTI); - efl_event_callback_add(widget, EFL_UI_EVENT_ITEM_SELECTED, _set_pointer_quit, &selected); - efl_event_callback_add(widget, EFL_UI_EVENT_ITEM_UNSELECTED, _set_pointer_quit, &unselected); + efl_event_callback_add(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, (Efl_Event_Cb) event_callback_single_call_int_data, &c); + efl_event_callback_add(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, event_callback_that_quits_the_main_loop_when_called, NULL); efl_ui_selectable_selected_set(efl_pack_content_get(widget, 0), EINA_TRUE); - selected = NULL;//No need to ckeck the flag, we asserted in the tcase before + c = 0; efl_ui_selectable_selected_set(efl_pack_content_get(widget, 2), EINA_TRUE); - selected = NULL;//No need to ckeck the flag, we asserted in the tcase before - unselected = NULL; + c = 0; efl_ui_selectable_selected_set(efl_pack_content_get(widget, 0), EINA_FALSE); - ck_assert_ptr_eq(selected, NULL); - ck_assert_ptr_eq(unselected, efl_pack_content_get(widget, 0)); - selected = NULL; - unselected = NULL; + if (c == 0) efl_loop_begin(efl_main_loop_get()); + ck_assert_int_eq(c, 1); + c = 0; efl_ui_selectable_selected_set(efl_pack_content_get(widget, 2), EINA_FALSE); - ck_assert_ptr_eq(selected, NULL); - ck_assert_ptr_eq(unselected, efl_pack_content_get(widget, 2)); - selected = NULL; - unselected = NULL; + if (c == 0) efl_loop_begin(efl_main_loop_get()); + ck_assert_int_eq(c, 1); + c = 0; ck_assert_ptr_eq(efl_ui_single_selectable_last_selected_get(widget), NULL); _iterator_to_array(&arr_selected, efl_ui_selected_items_get(widget)); ck_assert_int_eq(eina_array_count(arr_selected), 0); - efl_event_callback_del(widget, EFL_UI_EVENT_ITEM_SELECTED, _set_pointer_quit, &selected); - efl_event_callback_del(widget, EFL_UI_EVENT_ITEM_UNSELECTED, _set_pointer_quit, &unselected); + efl_event_callback_del(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, (Efl_Event_Cb) event_callback_single_call_int_data, &c); + efl_event_callback_del(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, event_callback_that_quits_the_main_loop_when_called, NULL); } EFL_END_TEST EFL_START_TEST(test_single_select) { - Efl_Ui_Item *selected = NULL; - Efl_Ui_Item *unselected = NULL; + int c = 0; Eina_Array *arr_selected; efl_ui_select_mode_set(widget, EFL_UI_SELECT_MODE_SINGLE); - efl_event_callback_add(widget, EFL_UI_EVENT_ITEM_SELECTED, _set_pointer_quit, &selected); - efl_event_callback_add(widget, EFL_UI_EVENT_ITEM_UNSELECTED, _set_pointer_quit, &unselected); + efl_event_callback_add(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, (Efl_Event_Cb) event_callback_single_call_int_data, &c); + efl_event_callback_add(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, event_callback_that_quits_the_main_loop_when_called, NULL); efl_ui_selectable_selected_set(efl_pack_content_get(widget, 0), EINA_TRUE); - ck_assert_ptr_eq(selected, efl_pack_content_get(widget, 0)); - ck_assert_ptr_eq(unselected, NULL); - selected = NULL; - unselected = NULL; + if (c == 0) efl_loop_begin(efl_main_loop_get()); + ck_assert_int_eq(c, 1); + c = 0; efl_ui_selectable_selected_set(efl_pack_content_get(widget, 2), EINA_TRUE); - ck_assert_ptr_eq(selected, efl_pack_content_get(widget, 2)); - ck_assert_ptr_eq(unselected, efl_pack_content_get(widget, 0)); - selected = NULL; - unselected = NULL; + if (c == 0) efl_loop_begin(efl_main_loop_get()); + ck_assert_int_eq(c, 1); + c = 0; ck_assert_int_eq(efl_ui_selectable_selected_get(efl_pack_content_get(widget, 0)), EINA_FALSE); ck_assert_int_eq(efl_ui_selectable_selected_get(efl_pack_content_get(widget, 2)), EINA_TRUE); ck_assert_ptr_eq(efl_ui_single_selectable_last_selected_get(widget), efl_pack_content_get(widget, 2)); @@ -138,48 +128,42 @@ EFL_START_TEST(test_single_select) ck_assert_ptr_eq(eina_array_data_get(arr_selected, 0), efl_pack_content_get(widget, 2)); efl_ui_selectable_selected_set(efl_pack_content_get(widget, 2), EINA_TRUE); - ck_assert_ptr_eq(selected, NULL); - ck_assert_ptr_eq(unselected, NULL); - selected = NULL; - unselected = NULL; - efl_event_callback_del(widget, EFL_UI_EVENT_ITEM_SELECTED, _set_pointer_quit, &selected); - efl_event_callback_del(widget, EFL_UI_EVENT_ITEM_UNSELECTED, _set_pointer_quit, &unselected); + ck_assert_int_eq(c, 0); + + efl_event_callback_del(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, (Efl_Event_Cb) event_callback_single_call_int_data, &c); + efl_event_callback_del(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, event_callback_that_quits_the_main_loop_when_called, NULL); } EFL_END_TEST EFL_START_TEST(test_single_select_always) { - Efl_Ui_Item *selected = NULL; - Efl_Ui_Item *unselected = NULL; + int c = 0; Eina_Array *arr_selected; efl_ui_select_mode_set(widget, EFL_UI_SELECT_MODE_SINGLE_ALWAYS); - efl_event_callback_add(widget, EFL_UI_EVENT_ITEM_SELECTED, _set_pointer_quit, &selected); - efl_event_callback_add(widget, EFL_UI_EVENT_ITEM_UNSELECTED, _set_pointer_quit, &unselected); + efl_event_callback_add(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, (Efl_Event_Cb) event_callback_single_call_int_data, &c); + efl_event_callback_add(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, event_callback_that_quits_the_main_loop_when_called, NULL); efl_ui_selectable_selected_set(efl_pack_content_get(widget, 0), EINA_TRUE); - ck_assert_ptr_eq(selected, efl_pack_content_get(widget, 0)); - ck_assert_ptr_eq(unselected, NULL); - selected = NULL; - unselected = NULL; + if (c == 0) efl_loop_begin(efl_main_loop_get()); + ck_assert_int_eq(c, 1); + c = 0; efl_ui_selectable_selected_set(efl_pack_content_get(widget, 2), EINA_TRUE); - ck_assert_ptr_eq(selected, efl_pack_content_get(widget, 2)); - ck_assert_ptr_eq(unselected, efl_pack_content_get(widget, 0)); - selected = NULL; - unselected = NULL; + if (c == 0) efl_loop_begin(efl_main_loop_get()); + ck_assert_int_eq(c, 1); + c = 0; efl_ui_selectable_selected_set(efl_pack_content_get(widget, 2), EINA_TRUE); - ck_assert_ptr_eq(selected, efl_pack_content_get(widget, 2)); - ck_assert_ptr_eq(unselected, NULL); - selected = NULL; - unselected = NULL; + if (c == 0) efl_loop_begin(efl_main_loop_get()); + ck_assert_int_eq(c, 1); + c = 0; ck_assert_int_eq(efl_ui_selectable_selected_get(efl_pack_content_get(widget, 0)), EINA_FALSE); ck_assert_int_eq(efl_ui_selectable_selected_get(efl_pack_content_get(widget, 2)), EINA_TRUE); ck_assert_ptr_eq(efl_ui_single_selectable_last_selected_get(widget), efl_pack_content_get(widget, 2)); _iterator_to_array(&arr_selected, efl_ui_selected_items_get(widget)); ck_assert_int_eq(eina_array_count(arr_selected), 1); ck_assert_ptr_eq(eina_array_data_get(arr_selected, 0), efl_pack_content_get(widget, 2)); - efl_event_callback_del(widget, EFL_UI_EVENT_ITEM_SELECTED, _set_pointer_quit, &selected); - efl_event_callback_del(widget, EFL_UI_EVENT_ITEM_UNSELECTED, _set_pointer_quit, &unselected); + efl_event_callback_del(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, (Efl_Event_Cb) event_callback_single_call_int_data, &c); + efl_event_callback_del(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, event_callback_that_quits_the_main_loop_when_called, NULL); } EFL_END_TEST diff --git a/src/tests/elementary/spec/efl_test_single_selectable.c b/src/tests/elementary/spec/efl_test_single_selectable.c index c62f78afc4..fd4b5e0a8b 100644 --- a/src/tests/elementary/spec/efl_test_single_selectable.c +++ b/src/tests/elementary/spec/efl_test_single_selectable.c @@ -33,11 +33,19 @@ _setup(void) EFL_START_TEST(last_selectable_check) { + int c = 0; + + efl_event_callback_add(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, (Efl_Event_Cb) event_callback_single_call_int_data, &c); + efl_event_callback_add(widget, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, event_callback_that_quits_the_main_loop_when_called, NULL); + Eo *c1 = efl_pack_content_get(widget, 0); Eo *c2 = efl_pack_content_get(widget, 2); efl_ui_selectable_selected_set(c1, EINA_TRUE); ck_assert_ptr_eq(efl_ui_single_selectable_last_selected_get(widget), c1); + if (c == 0) efl_loop_begin(efl_main_loop_get()); + ck_assert_int_eq(c, 1); + c = 0; efl_ui_selectable_selected_set(c2, EINA_TRUE); ck_assert_ptr_eq(efl_ui_single_selectable_last_selected_get(widget), c2); @@ -48,9 +56,26 @@ EFL_START_TEST(last_selectable_check) } EFL_END_TEST +EFL_START_TEST(fallback_selection) +{ + Eo *c1 = efl_pack_content_get(widget, 0); + Eo *c2 = efl_pack_content_get(widget, 2); + + efl_ui_single_selectable_fallback_selection_set(widget, c2); + ck_assert_int_eq(efl_ui_selectable_selected_get(c2), EINA_TRUE); + efl_ui_selectable_selected_set(c1, EINA_TRUE); + ck_assert_int_eq(efl_ui_selectable_selected_get(c2), EINA_FALSE); + ck_assert_int_eq(efl_ui_selectable_selected_get(c1), EINA_TRUE); + efl_ui_selectable_selected_set(c1, EINA_FALSE); + ck_assert_int_eq(efl_ui_selectable_selected_get(c2), EINA_TRUE); + ck_assert_int_eq(efl_ui_selectable_selected_get(c1), EINA_FALSE); +} +EFL_END_TEST + void efl_ui_single_selectable_behavior_test(TCase *tc) { tcase_add_checked_fixture(tc, _setup, NULL); tcase_add_test(tc, last_selectable_check); + tcase_add_test(tc, fallback_selection); }