cedric pushed a commit to branch master. http://git.enlightenment.org/core/elementary.git/commit/?id=015db15c626b9b06116fcc8f7ec61bbae3c6cc30
commit 015db15c626b9b06116fcc8f7ec61bbae3c6cc30 Author: divyesh purohit <div.puro...@samsung.com> Date: Thu Mar 10 14:24:50 2016 -0800 combobox: add Multiple selection feature Summary: This patch focuses on Combobox widget customization, Multibuttonentry widget is used instead of entry for taking user input. The idea is to make the widget look like {F28112} {F28115} when the multiple_selection is set. To-DO: 1) Need to add scrollable interface to combobox when MBE is used (need some suggestions on it). 2) focus cycle is still buggy as genlist requires focus otherwise selected item will return NULL (sometimes) Signed-off-by: divyesh purohit <div.puro...@samsung.com> @feature Test Plan: Please run combobox multiple selection example from elementart_test. Reviewers: raster, shilpasingh, cedric Subscribers: govi, rajeshps Projects: #elementary Differential Revision: https://phab.enlightenment.org/D3570 --- data/objects/Makefile.am | 8 ++- data/objects/combobox_multiple.edc | 61 ++++++++++++++++++ src/bin/test.c | 2 + src/bin/test_combobox.c | 102 +++++++++++++++++++++++++++++- src/lib/elc_combobox.c | 126 ++++++++++++++++++++++++++++++++++--- src/lib/elm_combobox.eo | 21 ++++++- src/lib/elm_widget_combobox.h | 2 + 7 files changed, 312 insertions(+), 10 deletions(-) diff --git a/data/objects/Makefile.am b/data/objects/Makefile.am index f8f3778..86593ca 100644 --- a/data/objects/Makefile.am +++ b/data/objects/Makefile.am @@ -7,7 +7,7 @@ EDJE_CC_FLAGS += -id $(top_srcdir)/data/objects -fd $(top_srcdir)/data/objects filesdir = $(datadir)/elementary/objects -files_DATA = test.edj test_external.edj multip.edj cursors.edj font_preview.edj postit_ent.edj multibuttonentry.edj test_prefs.edj test_prefs.epb test_focus_style.edj +files_DATA = test.edj test_external.edj multip.edj cursors.edj combobox_multiple.edj font_preview.edj postit_ent.edj multibuttonentry.edj test_prefs.edj test_prefs.epb test_focus_style.edj EXTRA_DIST = \ test.edc \ @@ -16,6 +16,7 @@ test_prefs.edc \ test_prefs.epc \ multip.edc \ cursors.edc \ +combobox_multiple.edc \ font_preview.edc \ postit_ent.edc \ multibuttonentry.edc \ @@ -57,6 +58,11 @@ cursors.edj: Makefile $(EXTRA_DIST) $(top_srcdir)/data/objects/cursors.edc \ $(top_builddir)/data/objects/cursors.edj +combobox_multiple.edj: Makefile combobox_multiple.edc + $(AM_V_EDJ)$(EDJE_CC) $(EDJE_CC_FLAGS) \ + $(top_srcdir)/data/objects/combobox_multiple.edc \ + $(top_builddir)/data/objects/combobox_multiple.edj + font_preview.edj: Makefile $(EXTRA_DIST) $(AM_V_EDJ)$(EDJE_CC) $(EDJE_CC_FLAGS) \ $(top_srcdir)/data/objects/font_preview.edc \ diff --git a/data/objects/combobox_multiple.edc b/data/objects/combobox_multiple.edc new file mode 100644 index 0000000..9654383 --- /dev/null +++ b/data/objects/combobox_multiple.edc @@ -0,0 +1,61 @@ +collections { +group { + name: "combobox_multiple_test"; + parts{ + part { + name: "bg"; + type: RECT; + mouse_events: 1; + scale:1; + description { + state: "default" 0.0; + color: 0 0 0 0; + rel1.relative: 0.0 0.0; + rel2.relative: 1.0 1.0; + } + } + part{ + name: "top.left"; + type: RECT; + scale: 1; + description { + state: "default" 0.0; + min : 0 0; + fixed: 1 1; + rel1 { relative: 0.0 0.0; to: bg; } + rel2 { relative: 0.0 0.0; to: bg; } + align: 0.0 0.0; + color: 0 0 0 0; + } + } + part{ + name: "bottom.right"; + type: RECT; + scale: 1; + description { + state: "default" 0.0; + min : 0 0; + fixed: 1 1; + rel1 { relative: 1.0 1.0; to: bg; } + rel2 { relative: 1.0 1.0; to: bg; } + align: 1.0 1.0; + color: 0 0 0 0; + } + } + part { + name: "combobox"; + type: SWALLOW; + mouse_events: 1; + scale:1; + description { + state: "default" 0.0; + min : 0 0; + max : -1 300; + rel1 { relative: 0.0 1.0; to: top.left; } + rel2 { relative: 0.0 0.0; to: bottom.right; } + align: 0.0 0.0; + } + } + } +} +} diff --git a/src/bin/test.c b/src/bin/test.c index a3ae31d..3bad1c6 100644 --- a/src/bin/test.c +++ b/src/bin/test.c @@ -46,6 +46,7 @@ void test_clock_edit(void *data, Evas_Object *obj, void *event_info); void test_clock_edit2(void *data, Evas_Object *obj, void *event_info); void test_clock_pause(void *data, Evas_Object *obj, void *event_info); void test_combobox(void *data, Evas_Object *obj, void *event_info); +void test_combobox2(void *data, Evas_Object *obj, void *event_info); void test_check(void *data, Evas_Object *obj, void *event_info); void test_check_toggle(void *data, Evas_Object *obj, void *event_info); void test_radio(void *data, Evas_Object *obj, void *event_info); @@ -755,6 +756,7 @@ add_tests: ADD_TEST(NULL, "Selectors", "DaySelector", test_dayselector); ADD_TEST(NULL, "Selectors", "Main menu", test_main_menu); ADD_TEST(NULL, "Selectors", "Combobox", test_combobox); + ADD_TEST(NULL, "Selectors", "Combobox Multiple Selection", test_combobox2); //------------------------------// ADD_TEST(NULL, "Cursors", "Cursor", test_cursor); diff --git a/src/bin/test_combobox.c b/src/bin/test_combobox.c index 720e3c6..baa6962 100644 --- a/src/bin/test_combobox.c +++ b/src/bin/test_combobox.c @@ -52,6 +52,14 @@ gl_text_get(void *data, Evas_Object *obj EINA_UNUSED, const char *part EINA_UNUS return strdup(buf); } +static char * +gl2_text_get(void *data, Evas_Object *obj EINA_UNUSED, const char *part EINA_UNUSED) +{ + char buf[256]; + snprintf(buf, sizeof(buf), "%s", (char*)data); + return strdup(buf); +} + static Evas_Object *gl_content_get(void *data EINA_UNUSED, Evas_Object *obj, const char *part) { @@ -76,9 +84,9 @@ static Eina_Bool gl_state_get(void *data EINA_UNUSED, static Eina_Bool gl_filter_get(void *data, Evas_Object *obj EINA_UNUSED, void *key) { + char buf[256]; // if the key is empty/NULL, return true for item if (!strlen((char *)key)) return EINA_TRUE; - char buf[256]; snprintf(buf, sizeof(buf), "Item # %i", (int)(uintptr_t)data); if (strcasestr(buf, (char *)key)) return EINA_TRUE; @@ -95,6 +103,27 @@ _gl_filter_restart_cb(void *data EINA_UNUSED, } static void +_gl2_filter_restart_cb(void *data EINA_UNUSED, + Evas_Object *obj, + void *event_info EINA_UNUSED) +{ + elm_genlist_filter_set(obj, (void *)elm_object_text_get(elm_multibuttonentry_entry_get(obj))); +} + +static Eina_Bool +gl2_filter_get(void *data, Evas_Object *obj EINA_UNUSED, void *key) +{ + char buf[256]; + // if the key is empty/NULL, return true for item + if (!strlen((char *)key)) return EINA_TRUE; + snprintf(buf, sizeof(buf), "%s", (char*)data); + if (!strncmp(buf, (char *)key, strlen((char*)key))) + return EINA_TRUE; + // Default case should return false (item fails filter hence will be hidden) + return EINA_FALSE; +} + +static void _gl_filter_finished_cb(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) @@ -176,3 +205,74 @@ test_combobox(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, evas_object_resize(win, 320, 500); evas_object_show(win); } + +static void +_combobox2_item_pressed_cb(void *data EINA_UNUSED, Evas_Object *obj, + void *event_info) +{ + const char *txt = elm_object_item_text_get(event_info); + printf("'item,pressed' callback is called. (selected item : %s)\n", txt); + if (elm_combobox_multiple_selection_get(obj)) + elm_multibuttonentry_item_append(obj, txt, NULL, NULL); + else + elm_object_text_set(obj, txt); + elm_combobox_hover_end(obj); +} +// Combobox with multiple selection +void +test_combobox2(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, + void *event_info EINA_UNUSED) +{ + Evas_Object *win, *combobox, *ly; + Elm_Genlist_Item_Class *itc; + char buf[128]; + char* email_address[300] = {"augue.ut.la...@suspendisse.com","egestas.aliquam....@vivamusmolestiedapibus.edu","urna...@purusnullam.co.uk","e...@sedid.net","cur...@malesuadafringillaest.net","lo...@cras.org","ri...@sedhendrerita.co.uk","auctor.nunc.nu...@utsemnulla.com","nunc.nu...@nonenim.org","eges...@egetipsum.co.uk","sed.tor...@tempusmauris.edu","rut...@gravida.org","n...@acsemut.com","lobortis.q...@eratvolutpatnulla.net","fa...@ullamcorperduisat.co.uk","pede.et.ri...@necurna.edu", [...] + win = elm_win_util_standard_add("combobox", "Combobox"); + elm_win_autodel_set(win, EINA_TRUE); + + ly = elm_layout_add(win); + snprintf(buf, sizeof(buf), "%s/objects/combobox_multiple.edj", elm_app_data_dir_get()); + elm_layout_file_set(ly, buf, "combobox_multiple_test"); + evas_object_size_hint_weight_set(ly, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_win_resize_object_add(win, ly); + evas_object_show(ly); + + itc = elm_genlist_item_class_new(); + itc->item_style = "default"; + itc->func.text_get = gl2_text_get; + itc->func.content_get = gl_content_get; + itc->func.state_get = gl_state_get; + itc->func.filter_get = gl2_filter_get; + itc->func.del = NULL; + + combobox = elm_combobox_add(win); + evas_object_size_hint_weight_set(combobox, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(combobox, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_object_text_set(combobox, "To:"); + elm_object_part_text_set(combobox, "guide", "Tap to add recipient"); + elm_combobox_multiple_selection_set(combobox, EINA_TRUE); + for (int i = 0; i < 300; i++) + elm_genlist_item_append(combobox, itc, (void *)email_address[i], + NULL, ELM_GENLIST_ITEM_NONE, NULL, + (void*)(uintptr_t)(i * 10)); + + evas_object_smart_callback_add(combobox, "clicked", + _combobox_clicked_cb, NULL); + evas_object_smart_callback_add(combobox, "item,selected", + _combobox_item_selected_cb, NULL); + evas_object_smart_callback_add(combobox, "dismissed", + _combobox_dismissed_cb, NULL); + evas_object_smart_callback_add(combobox, "expanded", + _combobox_expanded_cb, NULL); + evas_object_smart_callback_add(combobox, "item,pressed", + _combobox2_item_pressed_cb, NULL); + evas_object_smart_callback_add(combobox, "filter,done", + _gl_filter_finished_cb, NULL); + evas_object_smart_callback_add(combobox, "changed", + _gl2_filter_restart_cb, NULL); + elm_object_part_content_set(ly, "combobox", combobox); + evas_object_show(combobox); + + evas_object_resize(win, 640, 600); + evas_object_show(win); +} diff --git a/src/lib/elc_combobox.c b/src/lib/elc_combobox.c index 8f2b141..9719c13 100644 --- a/src/lib/elc_combobox.c +++ b/src/lib/elc_combobox.c @@ -150,7 +150,8 @@ _table_resize(void *data) evas_object_geometry_get(elm_object_item_track(sd->item), NULL, NULL, NULL, &h); if (h) sd->item_height = h; - evas_object_geometry_get(sd->entry, NULL, NULL, &obj_w, NULL); + evas_object_geometry_get(elm_object_part_content_get(data, "elm.swallow.content"), + NULL, NULL, &obj_w, NULL); evas_object_geometry_get(data, NULL, &obj_y, NULL, &obj_h); evas_object_geometry_get(sd->hover_parent, NULL, NULL, &hover_parent_w, &hover_parent_h); @@ -197,7 +198,13 @@ static void _on_item_selected(void *data , Evas_Object *obj EINA_UNUSED, void *event) { ELM_COMBOBOX_DATA_GET(data, sd); - elm_object_focus_set(sd->entry, EINA_TRUE); + + if (!sd->multiple_selection) elm_object_focus_set(sd->entry, EINA_TRUE); + else + { + elm_genlist_item_bring_in(sd->item, ELM_GENLIST_ITEM_SCROLLTO_TOP); + elm_object_focus_set(sd->mbe, EINA_TRUE); + } eo_event_callback_call(data, ELM_COMBOBOX_EVENT_ITEM_SELECTED, event); } @@ -305,6 +312,107 @@ _elm_combobox_elm_button_admits_autorepeat_get(Eo *obj EINA_UNUSED, return EINA_FALSE; } +EOLIAN static Eina_Bool +_elm_combobox_multiple_selection_get(Eo *obj EINA_UNUSED, Elm_Combobox_Data *pd) +{ + return pd->multiple_selection; +} + +static Eina_Bool +_mbe_clicked_cb(void *data EINA_UNUSED, Eo *obj, + const Eo_Event_Description *desc EINA_UNUSED, + void *event_info EINA_UNUSED) +{ + //Unset the multibuttonentry to contracted mode of single line + elm_multibuttonentry_expanded_set(obj, EINA_TRUE); + return EINA_TRUE; +} + +static Eina_Bool +_mbe_focused_cb(void *data EINA_UNUSED, Eo *obj EINA_UNUSED, + const Eo_Event_Description *desc EINA_UNUSED, + void *event_info EINA_UNUSED) +{ + return EINA_TRUE; +} + +static Eina_Bool +_mbe_unfocused_cb(void *data EINA_UNUSED, Eo *obj, + const Eo_Event_Description *desc EINA_UNUSED, + void *event_info EINA_UNUSED) +{ + //Set the multibuttonentry to contracted mode of single line + elm_multibuttonentry_expanded_set(obj, EINA_FALSE); + return EINA_TRUE; +} + +static Eina_Bool +_mbe_item_added(void *data, Eo *obj EINA_UNUSED, + const Eo_Event_Description *desc EINA_UNUSED, + void *event_info EINA_UNUSED) +{ + ELM_COMBOBOX_DATA_GET(data, sd); + elm_genlist_filter_set(sd->genlist, NULL); + return EINA_TRUE; +} + +EO_CALLBACKS_ARRAY_DEFINE(mbe_callbacks, + { EVAS_CLICKABLE_INTERFACE_EVENT_CLICKED, _mbe_clicked_cb }, + { ELM_WIDGET_EVENT_FOCUSED, _mbe_focused_cb }, + { ELM_WIDGET_EVENT_UNFOCUSED, _mbe_unfocused_cb }, + { ELM_MULTIBUTTONENTRY_EVENT_ITEM_ADDED , _mbe_item_added }); + +EO_CALLBACKS_ARRAY_DEFINE(entry_callbacks, + { ELM_ENTRY_EVENT_CHANGED_USER, _on_changed }, + { ELM_ENTRY_EVENT_ABORTED, _on_aborted }); + +EOLIAN static void +_elm_combobox_multiple_selection_set(Eo *obj, Elm_Combobox_Data *pd, + Eina_Bool enabled) +{ + Evas_Object* scr; + + pd->multiple_selection = enabled; + + if (enabled) + { + // This is multibuttonentry object that will take over the MBE call + eo_add(&pd->mbe,ELM_MULTIBUTTONENTRY_CLASS, obj); + evas_object_size_hint_weight_set(pd->mbe, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(pd->mbe, EVAS_HINT_FILL, EVAS_HINT_FILL); + eo_event_callback_array_add(elm_multibuttonentry_entry_get(pd->mbe), entry_callbacks(), obj); + eo_event_callback_array_add(pd->mbe, mbe_callbacks(), obj); + + pd->entry = elm_object_part_content_unset(obj, "elm.swallow.content"); + elm_object_text_set(pd->mbe, elm_object_part_text_get(pd->entry, NULL)); + elm_object_part_text_set(pd->mbe, "guide", elm_object_part_text_get(pd->entry, + "guide")); + evas_object_hide(pd->entry); + eo_composite_attach(obj, pd->mbe); + + scr = elm_scroller_add(obj); + elm_scroller_bounce_set(scr, EINA_FALSE, EINA_TRUE); + elm_scroller_policy_set(scr, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_AUTO); + evas_object_size_hint_weight_set(scr, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(scr, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(scr); + elm_object_content_set(scr, pd->mbe); + elm_object_part_content_set(obj, "elm.swallow.content", scr); + elm_widget_can_focus_set(pd->genlist, EINA_FALSE); + } + else + { + scr = elm_object_part_content_unset(obj, "elm.swallow.content"); + elm_object_part_content_set(obj, "elm.swallow.content", pd->entry); + elm_object_text_set(pd->entry, elm_object_part_text_get(pd->mbe, NULL)); + elm_object_part_text_set(pd->entry, "guide", + elm_object_part_text_get(pd->mbe, "guide")); + elm_widget_can_focus_set(pd->genlist, EINA_TRUE); + elm_genlist_item_bring_in(pd->item, ELM_GENLIST_ITEM_SCROLLTO_NONE); + evas_object_hide(scr); + } +} + EAPI Evas_Object * elm_combobox_add(Evas_Object *parent) { @@ -336,7 +444,6 @@ _elm_combobox_eo_base_constructor(Eo *obj, Elm_Combobox_Data *sd) eo_add(&sd->hover, ELM_HOVER_CLASS, sd->hover_parent); elm_widget_mirrored_automatic_set(sd->hover, EINA_FALSE); elm_hover_target_set(sd->hover, obj); - elm_widget_sub_object_add(obj, sd->hover); snprintf(buf, sizeof(buf), "combobox_vertical/%s", elm_widget_style_get(obj)); elm_object_style_set(sd->hover, buf); @@ -382,8 +489,7 @@ _elm_combobox_eo_base_constructor(Eo *obj, Elm_Combobox_Data *sd) ELM_SCROLLER_POLICY_OFF); elm_entry_scrollable_set(entry, EINA_TRUE); elm_entry_single_line_set(entry, EINA_TRUE); - eo_event_callback_add(entry, ELM_ENTRY_EVENT_CHANGED_USER, _on_changed, obj); - eo_event_callback_add(entry, ELM_ENTRY_EVENT_ABORTED, _on_aborted, obj); + eo_event_callback_array_add(entry, entry_callbacks(), obj); evas_object_show(entry); eo_composite_attach(obj, gl); @@ -397,7 +503,11 @@ EOLIAN static void _elm_combobox_hover_begin(Eo *obj, Elm_Combobox_Data *sd) { if (!sd->hover) return; - elm_object_focus_set(sd->entry, EINA_TRUE); + + if (sd->multiple_selection) + elm_object_focus_set(sd->mbe, EINA_TRUE); + else elm_object_focus_set(sd->entry, EINA_TRUE); + _activate(obj); } @@ -514,13 +624,15 @@ EOLIAN void _elm_combobox_elm_widget_part_text_set(Eo *obj EINA_UNUSED, Elm_Combobox_Data *pd, const char * part, const char *label) { - elm_object_part_text_set(pd->entry, part, label); + if (pd->multiple_selection) elm_object_part_text_set(pd->mbe, part, label); + else elm_object_part_text_set(pd->entry, part, label); } EOLIAN const char * _elm_combobox_elm_widget_part_text_get(Eo *obj EINA_UNUSED, Elm_Combobox_Data *pd, const char * part) { + if (pd->multiple_selection) return elm_object_part_text_get(pd->mbe, part); return elm_object_part_text_get(pd->entry, part); } diff --git a/src/lib/elm_combobox.eo b/src/lib/elm_combobox.eo index 598a462..4e711d4 100644 --- a/src/lib/elm_combobox.eo +++ b/src/lib/elm_combobox.eo @@ -1,6 +1,6 @@ class Elm.Combobox (Elm.Button, Evas.Selectable_Interface, Elm.Interface_Atspi_Widget_Action, - Elm.Entry, Elm.Genlist, Elm.Hover) + Elm.Entry, Elm.Genlist, Elm.Hover, Elm.Multibuttonentry) { eo_prefix: elm_obj_combobox; methods { @@ -16,6 +16,25 @@ class Elm.Combobox (Elm.Button, Evas.Selectable_Interface, return: bool; } } + @property multiple_selection { + get { + [[Returns whether the combobox allows multiple selection. + @since 1.18 + ]] + } + set { + [[Enables or disables multiple selection in combobox. + + Warning: This API should be set before any other API on + combobox, if you wish to avoid complications. + @since 1.18 + ]] + } + values { + enabled: bool; [[$true if multiple selection is enabled, + $false otherwise.]] + } + } hover_begin { [[This triggers the combobox popup from code, the same as if the user had clicked the button. diff --git a/src/lib/elm_widget_combobox.h b/src/lib/elm_widget_combobox.h index 205096a..9d578c5 100644 --- a/src/lib/elm_widget_combobox.h +++ b/src/lib/elm_widget_combobox.h @@ -33,6 +33,7 @@ struct _Elm_Combobox_Data Evas_Object *entry; Evas_Object *tbl; Evas_Object *spacer; + Evas_Object *mbe; Elm_Object_Item *item; const char *style; const char *best_location; @@ -40,6 +41,7 @@ struct _Elm_Combobox_Data int item_height; Eina_Bool expanded:1; Eina_Bool first_filter:1; + Eina_Bool multiple_selection:1; }; /** --