ryuan pushed a commit to branch master. http://git.enlightenment.org/core/elementary.git/commit/?id=1b07838b2c6e4c90664906077ff9f62bf0a63e57
commit 1b07838b2c6e4c90664906077ff9f62bf0a63e57 Author: Ryuan Choi <ryuan.c...@gmail.com> Date: Thu Oct 17 11:04:57 2013 +0900 Multiple selection support in fileselector Summary: Multiple selection support in fileselector Reviewers: seoz Reviewed By: seoz CC: seoz Differential Revision: https://phab.enlightenment.org/D207 --- ChangeLog | 4 + NEWS | 1 + src/bin/test_fileselector.c | 30 ++++- src/lib/elc_fileselector.c | 223 +++++++++++++++++++++++++++++++++++++- src/lib/elc_fileselector_eo.h | 39 +++++++ src/lib/elc_fileselector_legacy.h | 56 ++++++++++ src/lib/elm_widget_fileselector.h | 10 ++ 7 files changed, 359 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 68aba3a..7cba68f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1699,3 +1699,7 @@ * hoversel: Support elm_object_item_signal_emit in hoversel. This works only when the item is created. + +2013-10-17 Ryuan Choi (ryuan) + + * fileselector : Add support multi-selection. diff --git a/NEWS b/NEWS index a3bea3f..7c21ff0 100644 --- a/NEWS +++ b/NEWS @@ -97,6 +97,7 @@ Additions: * Add the configuration fileselector_double_tap_navigation_enable. * Add "activated" smart callback for fileselector. * Add elm_object_item_signal_emit support in hoversel. This works only when the item is created. + * Add elm_fileselector_multi_select_set/get() and elm_fileselector_selected_paths_get to support multi-selection. Improvements: diff --git a/src/bin/test_fileselector.c b/src/bin/test_fileselector.c index a7b1c9b..fa01be8 100644 --- a/src/bin/test_fileselector.c +++ b/src/bin/test_fileselector.c @@ -40,7 +40,17 @@ my_fileselector_selected(void *data EINA_UNUSED, printf("Selected file: %s\n", selected); /* or you can query the selection */ - printf("or: %s\n", elm_fileselector_selected_get(obj)); + if (elm_fileselector_multi_select_get(obj)) + { + const Eina_List *li; + const Eina_List *paths = elm_fileselector_selected_paths_get(obj); + char *path; + printf("All selected files are:\n"); + EINA_LIST_FOREACH(paths, li, path) + printf(" %s\n", path); + } + else + printf("or: %s\n", elm_fileselector_selected_get(obj)); } static void @@ -122,6 +132,17 @@ _expandable_clicked(void *data, } static void +_multi_clicked(void *data, + Evas_Object *obj EINA_UNUSED, + void *event_info EINA_UNUSED) +{ + Evas_Object *fs = data; + Eina_Bool enabled = elm_fileselector_multi_select_get(fs); + printf("Toggle Multiple selection to: %s\n", !enabled ? "On" : "Off"); + elm_fileselector_multi_select_set(fs, !enabled); +} + +static void _buttons_clicked(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) @@ -249,6 +270,13 @@ test_fileselector(void *data EINA_UNUSED, evas_object_show(bt); bt = elm_check_add(win); + elm_object_text_set(bt, "multiple selection"); + elm_check_state_set(bt, elm_fileselector_multi_select_get(fs)); + evas_object_smart_callback_add(bt, "changed", _multi_clicked, fs); + elm_box_pack_end(hbox, bt); + evas_object_show(bt); + + bt = elm_check_add(win); elm_object_text_set(bt, "buttons"); elm_check_state_set(bt, elm_fileselector_buttons_ok_cancel_get(fs)); evas_object_smart_callback_add(bt, "changed", _buttons_clicked, fs); diff --git a/src/lib/elc_fileselector.c b/src/lib/elc_fileselector.c index d608c81..3eb9956 100644 --- a/src/lib/elc_fileselector.c +++ b/src/lib/elc_fileselector.c @@ -4,7 +4,6 @@ * - user defined icon/label cb * - show/hide/add buttons ??? * - show/hide hidden files - * - multi-selection * - make variable/function names that are sensible * - Pattern Filter support * - Custom Filter support @@ -376,6 +375,13 @@ _signal_first(Listing_Request *lreq) { if (!lreq->first) return; + if (lreq->sd->multi) + { + char *path; + EINA_LIST_FREE(lreq->sd->paths, path) + free(path); + } + evas_object_smart_callback_call (lreq->obj, SIG_DIRECTORY_OPEN, (void *)lreq->path); @@ -511,6 +517,13 @@ _populate(Evas_Object *obj, it = eina_file_stat_ls(path); if (!it) return; + if (sd->multi) + { + char *path; + EINA_LIST_FREE(sd->paths, path) + free(path); + } + evas_object_smart_callback_call(obj, SIG_DIRECTORY_OPEN, (void *)path); if (!parent_it) { @@ -731,6 +744,34 @@ _on_item_activated(void *data, } static void +_clear_selections(Elm_Fileselector_Smart_Data *sd, Elm_Object_Item *last_selected) +{ + Eina_List *items; + Elm_Object_Item *sel; + + if (sd->mode == ELM_FILESELECTOR_LIST) + { + items = eina_list_clone(elm_genlist_selected_items_get(sd->files_list)); + + EINA_LIST_FREE(items, sel) + { + if (sel == last_selected) continue; + elm_genlist_item_selected_set(sel, EINA_FALSE); + } + } + else if (sd->mode == ELM_FILESELECTOR_GRID) + { + items = eina_list_clone(elm_gengrid_selected_items_get(sd->files_grid)); + + EINA_LIST_FREE(items, sel) + { + if (sel == last_selected) continue; + elm_gengrid_item_selected_set(sel, EINA_FALSE); + } + } +} + +static void _on_item_selected(void *data, Evas_Object *obj __UNUSED__, void *event_info) @@ -755,11 +796,46 @@ _on_item_selected(void *data, * - path is file and mode is NOT ONLY FOLDER */ if (is_dir == sd->only_folder) { - elm_object_text_set(sd->name_entry, ecore_file_file_get(path)); + if (sd->multi) + { + Eina_List *li; + const char *p; + Eina_Strbuf *buf; + + if (sd->dir_selected) + { + _clear_selections(sd, it); + sd->dir_selected = EINA_FALSE; + } + + buf = eina_strbuf_new(); + EINA_LIST_FOREACH(sd->paths, li, p) + { + eina_strbuf_append(buf, ecore_file_file_get(p)); + eina_strbuf_append_length(buf, ", ", 2); + } + + sd->paths = eina_list_append(sd->paths, strdup(path)); + eina_strbuf_append(buf, ecore_file_file_get(path)); + + elm_object_text_set(sd->name_entry, eina_strbuf_string_get(buf)); + eina_strbuf_free(buf); + } + else + elm_object_text_set(sd->name_entry, ecore_file_file_get(path)); + evas_object_smart_callback_call(data, SIG_SELECTED, (void *)path); } else - elm_object_text_set(sd->name_entry, ""); + { + if (sd->multi && is_dir && sd->double_tap_navigation) + { + _clear_selections(sd, it); + sd->dir_selected = EINA_TRUE; + } + + elm_object_text_set(sd->name_entry, ""); + } /* We need to populate, if path is directory and: * - mode is GRID; @@ -801,6 +877,48 @@ _on_item_selected(void *data, } static void +_on_item_unselected(void *data, + Evas_Object *obj __UNUSED__, + void *event_info) +{ + Eina_List *li, *l; + char *path; + const char *unselected_path; + Eina_Strbuf *buf; + Elm_Object_Item *it = event_info; + Eina_Bool first = EINA_TRUE; + + ELM_FILESELECTOR_DATA_GET(data, sd); + + if (!sd->multi) return; + + unselected_path = elm_object_item_data_get(it); + if (!unselected_path) return; + + buf = eina_strbuf_new(); + EINA_LIST_FOREACH_SAFE(sd->paths, li, l, path) + { + if (!strcmp(path, unselected_path)) + { + sd->paths = eina_list_remove_list(sd->paths, li); + free(path); + } + else + { + if (!first) + eina_strbuf_append_length(buf, ", ", 2); + else + first = EINA_FALSE; + + eina_strbuf_append(buf, ecore_file_file_get(path)); + } + } + + elm_object_text_set(sd->name_entry, eina_strbuf_string_get(buf)); + eina_strbuf_free(buf); +} + +static void _on_dir_up(void *data, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__) @@ -1083,6 +1201,7 @@ _elm_fileselector_smart_add(Eo *obj, void *_pd, va_list *list EINA_UNUSED) elm_gengrid_align_set(grid, 0.0, 0.0); evas_object_smart_callback_add(li, "selected", _on_item_selected, obj); + evas_object_smart_callback_add(li, "unselected", _on_item_unselected, obj); evas_object_smart_callback_add(li, "clicked,double", _on_item_double_clicked, obj); evas_object_smart_callback_add(li, "activated", _on_item_activated, obj); evas_object_smart_callback_add @@ -1092,6 +1211,7 @@ _elm_fileselector_smart_add(Eo *obj, void *_pd, va_list *list EINA_UNUSED) evas_object_smart_callback_add(li, "expanded", _on_list_expanded, obj); evas_object_smart_callback_add(li, "contracted", _on_list_contracted, obj); evas_object_smart_callback_add(grid, "selected", _on_item_selected, obj); + evas_object_smart_callback_add(grid, "unselected", _on_item_unselected, obj); evas_object_smart_callback_add(grid, "clicked,double", _on_item_double_clicked, obj); evas_object_smart_callback_add(grid, "activated", _on_item_activated, obj); @@ -1141,6 +1261,7 @@ _elm_fileselector_smart_del(Eo *obj EINA_UNUSED, void *_pd, va_list *list EINA_U { Elm_Fileselector_Smart_Data *sd = _pd; Elm_Fileselector_Filter *filter; + char *path; #ifdef HAVE_EIO if (sd->current) eio_file_cancel(sd->current); @@ -1156,6 +1277,9 @@ _elm_fileselector_smart_del(Eo *obj EINA_UNUSED, void *_pd, va_list *list EINA_U free(filter); } + EINA_LIST_FREE(sd->paths, path) + free(path); + sd->files_list = NULL; sd->files_grid = NULL; @@ -1439,6 +1563,70 @@ _mode_get(Eo *obj EINA_UNUSED, void *_pd, va_list *list) *ret = sd->mode; } +EAPI void +elm_fileselector_multi_select_set(Evas_Object *obj, Eina_Bool multi) +{ + ELM_FILESELECTOR_CHECK(obj); + eo_do(obj, elm_obj_fileselector_multi_select_set(multi)); +} + +static void +_multi_select_set(Eo *obj __UNUSED__, void *_pd, va_list *list __UNUSED__) +{ + Eina_Bool multi = va_arg(*list, int); + Elm_Fileselector_Smart_Data *sd = _pd; + char *path; + + multi = !!multi; + if (sd->multi == multi) return; + sd->multi = multi; + + elm_genlist_multi_select_set(sd->files_list, multi); + elm_gengrid_multi_select_set(sd->files_grid, multi); + + if (!sd->multi) + { + _clear_selections(sd, NULL); + + EINA_LIST_FREE(sd->paths, path) + free(path); + } + else + { + const Eina_List *selected_items, *li; + const Elm_Object_Item *it; + + if (sd->mode == ELM_FILESELECTOR_LIST) + selected_items = elm_genlist_selected_items_get(sd->files_list); + else + selected_items = elm_gengrid_selected_items_get(sd->files_list); + + EINA_LIST_FOREACH(selected_items, li, it) + { + path = elm_object_item_data_get(it); + sd->paths = eina_list_append(sd->paths, strdup(path)); + } + } +} + +EAPI Eina_Bool +elm_fileselector_multi_select_get(const Evas_Object *obj) +{ + ELM_FILESELECTOR_CHECK(obj) EINA_FALSE; + Eina_Bool ret = EINA_FALSE; + eo_do((Eo *) obj, elm_obj_fileselector_multi_select_get(&ret)); + return ret; +} + +static void +_multi_select_get(Eo *obj EINA_UNUSED, void *_pd, va_list *list) +{ + Eina_Bool *ret = va_arg(*list, Eina_Bool *); + Elm_Fileselector_Smart_Data *sd = _pd; + + *ret = sd->multi; +} + EAPI const char * elm_fileselector_selected_get(const Evas_Object *obj) { @@ -1527,6 +1715,29 @@ clean_up: free(path); } +EAPI const Eina_List * +elm_fileselector_selected_paths_get(const Evas_Object* obj) +{ + ELM_FILESELECTOR_CHECK(obj) NULL; + const Eina_List *ret = NULL; + eo_do((Eo *) obj, elm_obj_fileselector_selected_paths_get(&ret)); + return ret; +} + +static void +_selected_paths_get(Eo *obj __UNUSED__, void *_pd, va_list *list) +{ + const Eina_List **ret = va_arg(*list, const Eina_List**); + Elm_Fileselector_Smart_Data *sd = _pd; + + if (!ret) return; + + if (sd->multi) + *ret = sd->paths; + else + *ret = NULL; +} + EAPI Eina_Bool elm_fileselector_mime_types_filter_append(Evas_Object *obj, const char *mime_type, const char *filter_name) { @@ -1687,8 +1898,11 @@ _class_constructor(Eo_Class *klass) EO_OP_FUNC(ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_PATH_GET), _path_get), EO_OP_FUNC(ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_MODE_SET), _mode_set), EO_OP_FUNC(ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_MODE_GET), _mode_get), + EO_OP_FUNC(ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_MULTI_SELECT_SET), _multi_select_set), + EO_OP_FUNC(ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_MULTI_SELECT_GET), _multi_select_get), EO_OP_FUNC(ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_SELECTED_GET), _selected_get), EO_OP_FUNC(ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_SELECTED_SET), _selected_set), + EO_OP_FUNC(ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_SELECTED_PATHS_GET), _selected_paths_get), EO_OP_FUNC(ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_MIME_TYPES_FILTER_APPEND), _mime_types_filter_append), EO_OP_FUNC(ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_FILTERS_CLEAR), _filters_clear), EO_OP_FUNC_SENTINEL @@ -1736,8 +1950,11 @@ static const Eo_Op_Description op_desc[] = { EO_OP_DESCRIPTION(ELM_OBJ_FILESELECTOR_SUB_ID_PATH_GET, "Get the parent directory's path that a given file selector widget is displaying."), EO_OP_DESCRIPTION(ELM_OBJ_FILESELECTOR_SUB_ID_MODE_SET, "Set the mode in which a given file selector widget will display (layout) file system entries in its view."), EO_OP_DESCRIPTION(ELM_OBJ_FILESELECTOR_SUB_ID_MODE_GET, "Get the mode in which a given file selector widget is displaying (layouting) file system entries in its view."), + EO_OP_DESCRIPTION(ELM_OBJ_FILESELECTOR_SUB_ID_MULTI_SELECT_SET, "Enable or disable multi-selection in the file selector widget."), + EO_OP_DESCRIPTION(ELM_OBJ_FILESELECTOR_SUB_ID_MULTI_SELECT_GET, "Get if multi-selection in file selector widget is enabled or disabled."), EO_OP_DESCRIPTION(ELM_OBJ_FILESELECTOR_SUB_ID_SELECTED_GET, "Get the currently selected item's (full) path, in the given file selector widget."), EO_OP_DESCRIPTION(ELM_OBJ_FILESELECTOR_SUB_ID_SELECTED_SET, "Set, programmatically, the currently selected file/directory in the given file selector widget."), + EO_OP_DESCRIPTION(ELM_OBJ_FILESELECTOR_SUB_ID_SELECTED_PATHS_GET, "Get the currently selected item's (full) path, in the given file selector widget."), EO_OP_DESCRIPTION(ELM_OBJ_FILESELECTOR_SUB_ID_MIME_TYPES_FILTER_APPEND, "Append mime type filter"), EO_OP_DESCRIPTION(ELM_OBJ_FILESELECTOR_SUB_ID_FILTERS_CLEAR, "Clear filters"), EO_OP_DESCRIPTION_SENTINEL diff --git a/src/lib/elc_fileselector_eo.h b/src/lib/elc_fileselector_eo.h index 72fbedf..351c9b7 100644 --- a/src/lib/elc_fileselector_eo.h +++ b/src/lib/elc_fileselector_eo.h @@ -23,8 +23,11 @@ enum ELM_OBJ_FILESELECTOR_SUB_ID_PATH_GET, ELM_OBJ_FILESELECTOR_SUB_ID_MODE_SET, ELM_OBJ_FILESELECTOR_SUB_ID_MODE_GET, + ELM_OBJ_FILESELECTOR_SUB_ID_MULTI_SELECT_SET, + ELM_OBJ_FILESELECTOR_SUB_ID_MULTI_SELECT_GET, ELM_OBJ_FILESELECTOR_SUB_ID_SELECTED_GET, ELM_OBJ_FILESELECTOR_SUB_ID_SELECTED_SET, + ELM_OBJ_FILESELECTOR_SUB_ID_SELECTED_PATHS_GET, ELM_OBJ_FILESELECTOR_SUB_ID_MIME_TYPES_FILTER_APPEND, ELM_OBJ_FILESELECTOR_SUB_ID_FILTERS_CLEAR, ELM_OBJ_FILESELECTOR_SUB_ID_LAST @@ -183,6 +186,30 @@ enum #define elm_obj_fileselector_mode_get(ret) ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_MODE_GET), EO_TYPECHECK(Elm_Fileselector_Mode *, ret) /** + * @def elm_obj_fileselector_multi_select_set + * @since 1.8 + * + * Enable or disable multi-selection in the fileselector + * + * @param[in] multi + * + * @see elm_fileselector_multi_select_set + */ +#define elm_obj_fileselector_multi_select_set(multi) ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_MULTI_SELECT_SET), EO_TYPECHECK(Eina_Bool, multi) + +/** + * @def elm_obj_fileselector_multi_select_get + * @since 1.8 + * + * Gets if multi-selection in fileselector is enabled or disabled. + * + * @param[out] multi + * + * @see elm_fileselector_multi_select_get + */ +#define elm_obj_fileselector_multi_select_get(ret) ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_MULTI_SELECT_GET), EO_TYPECHECK(Eina_Bool *, ret) + +/** * @def elm_obj_fileselector_selected_get * @since 1.8 * @@ -210,6 +237,18 @@ enum #define elm_obj_fileselector_selected_set(_path, ret) ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_SELECTED_SET), EO_TYPECHECK(const char *, _path), EO_TYPECHECK(Eina_Bool *, ret) /** + * @def elm_obj_fileselector_selected_paths_get + * @since 1.8 + * + * Get a list of selected paths in the fileselector. + * + * @param[out] ret + * + * @see elm_fileselector_selected_paths_get + */ +#define elm_obj_fileselector_selected_paths_get(ret) ELM_OBJ_FILESELECTOR_ID(ELM_OBJ_FILESELECTOR_SUB_ID_SELECTED_PATHS_GET), EO_TYPECHECK(const Eina_List **, ret) + +/** * @def elm_obj_fileselector_mime_types_filter_append * @since 1.8 * diff --git a/src/lib/elc_fileselector_legacy.h b/src/lib/elc_fileselector_legacy.h index 6c89635..c94a724 100644 --- a/src/lib/elc_fileselector_legacy.h +++ b/src/lib/elc_fileselector_legacy.h @@ -211,6 +211,39 @@ EAPI void elm_fileselector_mode_set(Evas_Object *obj, Elm_Files EAPI Elm_Fileselector_Mode elm_fileselector_mode_get(const Evas_Object *obj); /** + * Enable or disable multi-selection in the file selector widget. + * + * @param obj The file selector object + * @param multi Multi-select enable/disable. Default is disabled. + * + * This enables (@c EINA_TRUE) or disables (@c EINA_FALSE) multi-selection in + * the list/grid of the file selector widget. This allows more than 1 item to + * be selected. To retrieve the list of selected paths, use + * elm_fileselector_selected_paths_get(). + * + * @see elm_fileselector_selected_paths_get() + * @see elm_fileselector_multi_select_get() + * + * @since 1.8 + * @ingroup Fileselector + */ +EAPI void elm_fileselector_multi_select_set(Evas_Object *obj, Eina_Bool multi); + +/** + * Get if multi-selection in the file selector is enabled or disabled. + * + * @param obj The file selector object + * @return Multi-select enabled/disabled + * (@c EINA_TRUE = enabled/@c EINA_FALSE = disabled). Default is @c EINA_FALSE. + * + * @see elm_fileselector_multi_select_set() + * + * @since 1.8 + * @ingroup Fileselector + */ +EAPI Eina_Bool elm_fileselector_multi_select_get(const Evas_Object *obj); + +/** * Set, programmatically, the currently selected file/directory in * the given file selector widget * @@ -244,6 +277,29 @@ EAPI Eina_Bool elm_fileselector_selected_set(Evas_Object *obj, const EAPI const char *elm_fileselector_selected_get(const Evas_Object *obj); /** + * Get a list of selected paths in the file selector. + * + * @param obj The file selector object + * @return The list of selected paths, or NULL if not in multi-select mode or none are selected. + * + * It returns a list of the selected paths. This list pointer is only valid so + * long as the selection doesn't change (no items are selected or unselected, or + * unselected implicitly by deletion). The list contains const char *. + * The order of the items in this list is the order which they were selected, + * i.e. the first item in this list is the first item that was selected, and so on. + * + * @note If not in multi-select mode, consider using function + * elm_fileselector_selected_get() instead. + * + * @see elm_fileselector_multi_select_set() + * @see elm_fileselector_selected_get() + * + * @since 1.8 + * @ingroup Fileselector + */ +EAPI const Eina_List *elm_fileselector_selected_paths_get(const Evas_Object *obj); + +/** * Append mime types filter into filter list * * @param obj The file selector object diff --git a/src/lib/elm_widget_fileselector.h b/src/lib/elm_widget_fileselector.h index 875a2b7..889e11b 100644 --- a/src/lib/elm_widget_fileselector.h +++ b/src/lib/elm_widget_fileselector.h @@ -39,6 +39,9 @@ struct _Elm_Fileselector_Smart_Data Eina_List *filter_list; Elm_Fileselector_Filter *current_filter; + /* a list of selected paths. only for multi selection */ + Eina_List *paths; + const char *path; const char *selection; Ecore_Idler *populate_idler; @@ -54,6 +57,13 @@ struct _Elm_Fileselector_Smart_Data Eina_Bool only_folder : 1; Eina_Bool expand : 1; Eina_Bool double_tap_navigation : 1; + Eina_Bool multi : 1; + + /* this flag is only for multi selection. + * If this flag is set to EINA_TRUE, it means directory is selected + * so that fileselector will clear current selection when user clicked + * another item. */ + Eina_Bool dir_selected : 1; }; struct sel_data --