From 61aeb2a6a262c657a67a53a836d39896e94efb94 Mon Sep 17 00:00:00 2001 From: Larry Jr Date: Wed, 1 Jul 2015 19:55:40 -0300 Subject: EFL Model Form View for MVC design Elementary view that show an EFL.model and keep them synchronized asynchronously. The elm_view_form connects properties of a data model to different widgets. There is an example defined in src/examples/filemvc.c that also uses a list view. You can add widgets connected to properties using: eo_do(formview, elm_view_form_widget_add("filename", _label_init(win, bxr, "File Name"))); @feature --- src/examples/.gitignore | 1 + src/examples/Makefile.am | 2 + src/examples/filemvc.c | 211 +++++++++++++++++++++++++++++++++++++ src/lib/Elementary.h.in | 1 + src/lib/Makefile.am | 3 + src/lib/elm_view_form.c | 266 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib/elm_view_form.eo | 30 ++++++ src/lib/elm_view_form.h | 10 ++ 8 files changed, 524 insertions(+) create mode 100644 src/examples/filemvc.c create mode 100644 src/lib/elm_view_form.c create mode 100644 src/lib/elm_view_form.eo create mode 100644 src/lib/elm_view_form.h diff --git a/src/examples/.gitignore b/src/examples/.gitignore index 7c9b93b9d..98a343b64 100644 --- a/src/examples/.gitignore +++ b/src/examples/.gitignore @@ -33,6 +33,7 @@ /fileselector_entry_example /fileselector_example /fileviewlist +/filemvc /flip_example_01 /flipselector_example /frame_example_01 diff --git a/src/examples/Makefile.am b/src/examples/Makefile.am index 3835b5f3e..ef68bdd4c 100644 --- a/src/examples/Makefile.am +++ b/src/examples/Makefile.am @@ -69,6 +69,7 @@ fileselector_button_example.c \ fileselector_entry_example.c \ fileselector_example.c \ fileviewlist.c \ +filemvc.c \ flip_example_01.c \ flipselector_example.c \ frame_example_01.c \ @@ -222,6 +223,7 @@ fileselector_button_example \ fileselector_entry_example \ fileselector_example \ fileviewlist \ +filemvc \ flip_example_01 \ flipselector_example \ frame_example_01 \ diff --git a/src/examples/filemvc.c b/src/examples/filemvc.c new file mode 100644 index 000000000..9334b499d --- /dev/null +++ b/src/examples/filemvc.c @@ -0,0 +1,211 @@ +//#ifdef HAVE_CONFIG_H +# include "../../elementary_config.h" +//#endif + +#include +#include +#include +#include +#include +#include +#include + +#define EFL_MODEL_TEST_FILENAME_PATH "/tmp" + +struct _Efl_Model_Test_Filemvc_Data +{ + Eo *fileview; + Eo *treeview; + Eo *formview; + Eo *treemodel; + Evas_Object *thumb; + char imagedefault_path[256]; +}; +typedef struct _Efl_Model_Test_Filemvc_Data Efl_Model_Test_Filemvc_Data; + +static void +_cleanup_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Efl_Model_Test_Filemvc_Data *priv = (Efl_Model_Test_Filemvc_Data *)data; + eo_unref(priv->fileview); + eo_unref(priv->treeview); + eo_unref(priv->formview); + eo_unref(priv->treemodel); +} + +static Eina_Bool +_filter_cb(void *data EINA_UNUSED, Eio_File *handler EINA_UNUSED, const Eina_File_Direct_Info *info) +{ + if (info->type == EINA_FILE_DIR && info->path[info->name_start] != '.') return EINA_TRUE; + + return EINA_FALSE; +} + +static Eina_Bool +_list_selected_cb(void *data EINA_UNUSED, Eo *obj EINA_UNUSED, const Eo_Event_Description *desc EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Efl_Model_Test_Filemvc_Data *priv = data; + Eo *child = event_info; + ethumb_client_file_free(elm_thumb_ethumb_client_get()); + + printf("LIST selected model\n"); + eo_do(priv->formview, elm_view_form_model_set(child)); + return EINA_TRUE; +} + +static Eina_Bool +_tree_selected_cb(void *data, Eo *obj EINA_UNUSED, const Eo_Event_Description *desc EINA_UNUSED, void *event_info) +{ + printf("TREE selected model\n"); + Efl_Model_Test_Filemvc_Data *priv = data; + Eo *child = event_info; + const Eina_Value *vpath; + Eo *model; + char *path; + + eo_do(child, efl_model_property_get("path", &vpath)); + eina_value_get(vpath, &path); + model = eo_add(EIO_MODEL_CLASS, NULL, eio_model_path_set(path)); + eo_do(model, efl_model_load()); + eo_do(priv->fileview, elm_view_list_model_set(model)); +// eo_unref(model); + return EINA_TRUE; +} + +static void +_widget_init(Evas_Object *widget) +{ + evas_object_size_hint_weight_set(widget, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(widget, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(widget); +} + +Evas_Object * +_label_init(Evas_Object *win, Evas_Object *box, const char *text) +{ + Evas_Object *widget = elm_label_add(win); + elm_label_line_wrap_set(widget, ELM_WRAP_CHAR); + elm_object_text_set(widget, text); + elm_box_pack_end(box, widget); + evas_object_size_hint_weight_set(widget, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_align_set(widget, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(widget); + + return widget; +} + + +static void +_thumb_error_cb(void *data, Evas_Object *o EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Efl_Model_Test_Filemvc_Data *priv = data; + printf("thumbnail generation error.\n"); + elm_thumb_file_set(priv->thumb, priv->imagedefault_path, NULL); + elm_thumb_reload(priv->thumb); +} + +EAPI_MAIN int +elm_main(int argc, char **argv) +{ + Efl_Model_Test_Filemvc_Data priv; + Evas_Object *win, *panes, *bxr, *genlist, *vpanes; + Evas_Object *entry; + char *dirname; + + memset(&priv, 0, sizeof(Efl_Model_Test_Filemvc_Data)); + elm_app_info_set(elm_main, "elementary", "images/logo.png"); + sprintf(priv.imagedefault_path, "%s/images/logo.png", elm_app_data_dir_get()); + + //win + win = elm_win_util_standard_add("viewlist", "Viewlist"); + elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED); + elm_win_autodel_set(win, EINA_TRUE); + + panes = elm_panes_add(win); +// elm_box_horizontal_set(box, EINA_TRUE); + evas_object_size_hint_weight_set(panes, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_win_resize_object_add(win, panes); + + ecore_init(); + eio_init(); + + if(argv[1] != NULL) dirname = argv[1]; + else dirname = EFL_MODEL_TEST_FILENAME_PATH; + + //treemodel + priv.treemodel = eo_add(EIO_MODEL_CLASS, NULL, eio_model_path_set(dirname)); + eo_do(priv.treemodel, eio_model_children_filter_set(_filter_cb, NULL)); + eo_do(priv.treemodel, efl_model_load()); + + //treeview + genlist = elm_genlist_add(win); + priv.treeview = eo_add(ELM_VIEW_LIST_CLASS, NULL, elm_view_list_genlist_set(genlist, ELM_GENLIST_ITEM_TREE, NULL)); + eo_do(priv.treeview, elm_view_list_property_connect("filename", "elm.text"), + elm_view_list_property_connect("icon", "elm.swallow.icon"), + elm_view_list_model_set(priv.treemodel)); + _widget_init(genlist); + elm_object_part_content_set(panes, "left", genlist); + eo_do(panes, elm_obj_panes_content_left_size_set(0.3)); + + vpanes = elm_panes_add(win); + _widget_init(vpanes); + elm_object_part_content_set(panes, "right", vpanes); + eo_do(priv.treeview, eo_event_callback_add(ELM_VIEW_LIST_EVENT_MODEL_SELECTED, _tree_selected_cb, &priv)); + //listview + genlist = elm_genlist_add(win); + priv.fileview = eo_add(ELM_VIEW_LIST_CLASS, NULL, elm_view_list_genlist_set(genlist, ELM_GENLIST_ITEM_NONE, "double_label")); + eo_do(priv.fileview, elm_view_list_property_connect("filename", "elm.text"), + elm_view_list_property_connect("size", "elm.text.sub")); + + evas_object_event_callback_add(win, EVAS_CALLBACK_DEL, _cleanup_cb, &priv); + _widget_init(genlist); + elm_object_part_content_set(vpanes, "left", genlist); + + eo_do(priv.fileview, eo_event_callback_add(ELM_VIEW_LIST_EVENT_MODEL_SELECTED, _list_selected_cb, &priv)); + + //formview + bxr = elm_box_add(win); + _widget_init(bxr); + elm_object_part_content_set(vpanes, "right", bxr); + priv.formview = eo_add(ELM_VIEW_FORM_CLASS, NULL); + + /*Label widget */ + eo_do(priv.formview, elm_view_form_widget_add("filename", _label_init(win, bxr, "File Name"))); + + _label_init(win, bxr, "Size:"); + eo_do(priv.formview, elm_view_form_widget_add("size", _label_init(win, bxr, ""))); + + _label_init(win, bxr, "Modified:"); + eo_do(priv.formview, elm_view_form_widget_add("mtime", _label_init(win, bxr, ""))); + + /* Entry widget */ + entry = elm_entry_add(win); + elm_entry_single_line_set(entry, EINA_TRUE); + evas_object_size_hint_weight_set(entry, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_box_pack_end(bxr, entry); + evas_object_show(entry); + eo_do(priv.formview, elm_view_form_widget_add("path", entry)); + + /* Thumb widget */ + elm_need_ethumb(); + priv.thumb = elm_thumb_add(win); + _widget_init(priv.thumb); + elm_box_pack_end(bxr, priv.thumb); + elm_thumb_editable_set(priv.thumb, EINA_FALSE); + eo_do(priv.formview, elm_view_form_widget_add("path", priv.thumb)); + evas_object_smart_callback_add(priv.thumb, "generate,error", _thumb_error_cb, &priv); + evas_object_smart_callback_add(priv.thumb, "load,error", _thumb_error_cb, &priv); + + //showall + evas_object_resize(win, 800, 400); + evas_object_show(panes); + evas_object_show(win); + + elm_run(); + elm_shutdown(); + ecore_shutdown(); + + return 0; +} +ELM_MAIN() + diff --git a/src/lib/Elementary.h.in b/src/lib/Elementary.h.in index 61cb53ea0..af3f1229f 100644 --- a/src/lib/Elementary.h.in +++ b/src/lib/Elementary.h.in @@ -261,6 +261,7 @@ EAPI extern Elm_Version *elm_version; #include #include #include +#include #include #include #include diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 547576709..fbdfc3948 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -377,6 +377,7 @@ elm_video.h \ elm_video_eo.h \ elm_video_legacy.h \ elm_view_list.h \ +elm_view_form.h \ elm_web.h \ elm_web_common.h \ elm_web_eo.h \ @@ -502,6 +503,7 @@ elm_util.c \ elm_url.c \ elm_video.c \ elm_view_list.c \ +elm_view_form.c \ elm_web2.c \ elm_widget.c \ elm_win.c \ @@ -622,6 +624,7 @@ elm_thumb.eo \ elm_toolbar.eo \ elm_video.eo \ elm_view_list.eo \ +elm_view_form.eo \ elm_web.eo \ elm_widget.eo \ elm_win.eo \ diff --git a/src/lib/elm_view_form.c b/src/lib/elm_view_form.c new file mode 100644 index 000000000..7437a95e7 --- /dev/null +++ b/src/lib/elm_view_form.c @@ -0,0 +1,266 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif +#include +#include +#include + +#include "elm_view_form.h" +#include "elm_view_list.h" + +#include "elm_priv.h" +#include + +#define MY_CLASS ELM_VIEW_FORM_CLASS +#define MY_CLASS_NAME "View_Form" + +typedef struct _Elm_View_Form_Data Elm_View_Form_Data; +typedef struct _Elm_View_Form_Widget Elm_View_Form_Widget; + +/** + * @brief Local-use callbacks + */ +typedef void (*Elm_View_Form_Event_Cb)(Elm_View_Form_Widget *, Elm_View_Form_Data *, Evas_Object *); +typedef void (*Elm_View_Form_Widget_Object_Set_Cb)(Eo *, Evas_Object *, const Eina_Value *, const char *); + +struct _Elm_View_Form_Widget +{ + Eina_Stringshare *widget_propname; + Evas_Object *widget_obj; + Elm_View_Form_Event_Cb widget_obj_value_update_cb; + Elm_View_Form_Widget_Object_Set_Cb widget_obj_set_cb; +}; + +struct _Elm_View_Form_Data +{ + Eo *model_obj; + Eina_Value *properties; + Eina_List *l; +}; + +static Eina_Bool +_efl_model_properties_change_cb(void *data, Eo *obj EINA_UNUSED, const Eo_Event_Description *desc EINA_UNUSED, void *event_info) +{ + const Efl_Model_Property_Event *evt = event_info; + const Eina_Value *value; + const char *prop; + unsigned int i; + Elm_View_Form_Data *priv = data; + Eina_List *l = NULL; + Elm_View_Form_Widget *w = NULL; + Eina_Array_Iterator it; + + EINA_SAFETY_ON_NULL_RETURN_VAL(priv, EINA_TRUE); + EINA_SAFETY_ON_NULL_RETURN_VAL(evt, EINA_TRUE); + + if (!evt->changed_properties) + return EINA_TRUE; + + //update all widgets with this property + EINA_LIST_FOREACH(priv->l, l, w) + { + EINA_ARRAY_ITER_NEXT(evt->changed_properties, i, prop, it) + { + if (!strcmp(w->widget_propname, prop)) + { + eo_do(priv->model_obj, efl_model_property_get(prop, &value)); + w->widget_obj_set_cb(priv->model_obj, w->widget_obj, value, w->widget_propname); + } + } + } + + return EINA_TRUE; +} + +static void +_update_model_properties(Elm_View_Form_Data *priv) +{ + const Eina_Value *value; + Eina_List *l; + Elm_View_Form_Widget *w; + //update all widgets property + EINA_LIST_FOREACH(priv->l, l, w) + { + eo_do(priv->model_obj, efl_model_property_get(w->widget_propname, &value)); + w->widget_obj_set_cb(priv->model_obj, w->widget_obj, value, w->widget_propname); + } +} + +/** + * @brief Set widget. + * Works, so far, for widget(s): Entry, Label + */ +static void +_elm_evas_object_text_set_cb(Eo *obj EINA_UNUSED, Evas_Object *widget, const Eina_Value *value, const char *propname EINA_UNUSED) +{ + const char *c_text = NULL; + char *text = NULL; + + EINA_SAFETY_ON_NULL_RETURN(value); + EINA_SAFETY_ON_NULL_RETURN(widget); + + text = eina_value_to_string(value); + EINA_SAFETY_ON_NULL_RETURN(text); + + c_text = elm_object_text_get(widget); + EINA_SAFETY_ON_NULL_RETURN(c_text); + + if (strcmp(text, c_text) != 0) + { + elm_object_text_set(widget, text); + } + free(text); +} + +static void +_elm_evas_object_thumb_set_cb(Eo *obj EINA_UNUSED, Evas_Object *thumb, const Eina_Value *value, const char *propname EINA_UNUSED) +{ + char *filename = NULL; + + EINA_SAFETY_ON_NULL_RETURN(value); + filename = eina_value_to_string(value); + EINA_SAFETY_ON_NULL_RETURN(filename); + if (strlen(filename) < PATH_MAX) + { + elm_thumb_file_set(thumb, filename, NULL); + elm_thumb_reload(thumb); + } + free(filename); +} + +/** + * @brief Evas object callback implementation. + * Updates Widget's value if not the same object + * and the widget itself. + */ +static void +_elm_evas_object_text_changed_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Eina_Value value; + Eina_List *l; + Elm_View_Form_Data *priv = (Elm_View_Form_Data *)data; + Elm_View_Form_Widget *w = NULL; + + EINA_LIST_FOREACH(priv->l, l, w) + { + if (w->widget_obj == obj) + break; + } + + EINA_SAFETY_ON_NULL_RETURN(w); + eina_value_setup(&value, EINA_VALUE_TYPE_STRING); + eina_value_set(&value, elm_object_text_get(obj)); + eo_do(priv->model_obj, efl_model_property_set(w->widget_propname, &value)); + eina_value_flush(&value); +} +/** + * @brief Add new widget object. + * Adds new widget object on the list + * and perform initial setup. + */ +static Eina_Bool +_elm_view_widget_add(Elm_View_Form_Data *priv, const char *propname, Evas_Object *widget_obj) +{ + const Eina_Value *value = NULL; + Elm_View_Form_Widget *w = calloc(1, sizeof(Elm_View_Form_Widget)); + EINA_SAFETY_ON_NULL_RETURN_VAL(w, EINA_FALSE); + + w->widget_propname = eina_stringshare_add(propname); + w->widget_obj = widget_obj; + priv->l = eina_list_append(priv->l, w); + + if(eo_isa(widget_obj, ELM_ENTRY_CLASS)) + { + w->widget_obj_set_cb = _elm_evas_object_text_set_cb; + evas_object_event_callback_add(w->widget_obj, EVAS_CALLBACK_KEY_DOWN, _elm_evas_object_text_changed_cb, priv); + } + else if(eo_isa(widget_obj, ELM_LABEL_CLASS)) + { + w->widget_obj_set_cb = _elm_evas_object_text_set_cb; + } + else if(eo_isa(widget_obj, ELM_THUMB_CLASS)) + { + w->widget_obj_set_cb = _elm_evas_object_thumb_set_cb; + } + else + { + // Widget yet not supported + EINA_SAFETY_ON_NULL_RETURN_VAL(NULL, EINA_FALSE); + } + + eo_do(priv->model_obj, efl_model_property_get(propname, &value)); + if (value) + { + w->widget_obj_set_cb(priv->model_obj, w->widget_obj, value, w->widget_propname); + } + + return EINA_TRUE; +} +/** + * Helper functions - End + */ + + +/** + * @brief constructor + */ +static Eo_Base* +_elm_view_form_eo_base_constructor(Eo *obj EINA_UNUSED, Elm_View_Form_Data *_pd) +{ + Elm_View_Form_Data *priv = (Elm_View_Form_Data *)_pd; + priv->model_obj = NULL; + + eo_do_super(obj, MY_CLASS, eo_constructor()); + + return obj; +} + +/** + * @brief destructor + */ +static void +_elm_view_form_eo_base_destructor(Eo *obj, Elm_View_Form_Data *priv) +{ + Elm_View_Form_Widget *w = NULL; + EINA_LIST_FREE(priv->l, w) + { + eina_stringshare_del(w->widget_propname); + free(w); + } + + eo_do_super(obj, MY_CLASS, eo_destructor()); +} + + +static void +_elm_view_form_model_set(Eo *obj EINA_UNUSED, Elm_View_Form_Data *priv, Eo *model) +{ + if (priv->model_obj != NULL) + { + eo_do(priv->model_obj, eo_event_callback_del(EFL_MODEL_BASE_EVENT_PROPERTIES_CHANGED, _efl_model_properties_change_cb, priv)); + eo_unref(priv->model_obj); + } + + priv->model_obj = model; + + if (priv->model_obj != NULL) + { + eo_ref(priv->model_obj); + eo_do(priv->model_obj, eo_event_callback_add(EFL_MODEL_BASE_EVENT_PROPERTIES_CHANGED, _efl_model_properties_change_cb, priv)); + _update_model_properties(priv); + } +} + +static void +_elm_view_form_widget_add(Eo *obj EINA_UNUSED, Elm_View_Form_Data *priv, const char *propname, Evas_Object *evas) +{ + Eina_Bool status; + + EINA_SAFETY_ON_NULL_RETURN(evas); + EINA_SAFETY_ON_NULL_RETURN(propname); + + status = _elm_view_widget_add(priv, propname, evas); + EINA_SAFETY_ON_FALSE_RETURN(status); +} + +#include "elm_view_form.eo.c" diff --git a/src/lib/elm_view_form.eo b/src/lib/elm_view_form.eo new file mode 100644 index 000000000..e91919f0d --- /dev/null +++ b/src/lib/elm_view_form.eo @@ -0,0 +1,30 @@ +class Elm_View_Form (Eo.Base) +{ + legacy_prefix: null; + methods { + model_set { + /*@ Set model + @def elm_view_form_model_set + @since 1.11 */ + params { + @in model: Efl.Model.Base*; /*@ Emodel object */ + } + } + widget_add { + /*@ Add new widget + @def elm_view_form_widget_add + @since 1.11 */ + params { + @in propname: const(char)*; /*@ Property name */ + @in evas: Evas_Object*; /*@ Evas widget */ + } + } + } + implements { + Eo.Base.destructor; + Eo.Base.constructor; + } + constructors { + .model_set; + } +} diff --git a/src/lib/elm_view_form.h b/src/lib/elm_view_form.h new file mode 100644 index 000000000..3572c7c72 --- /dev/null +++ b/src/lib/elm_view_form.h @@ -0,0 +1,10 @@ + +#ifdef EFL_EO_API_SUPPORT +#ifndef ELM_VIEW_FORM_H +#define ELM_VIEW_FORM_H + +#include +#include + +#endif +#endif -- cgit v1.2.1