From 29fcdd8a4b4fb5140a0070c6f87469f6662acb42 Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Wed, 28 Nov 2018 16:03:33 -0800 Subject: [PATCH] ecore: add an helper class Efl.Model_View With the advancement of our MVVM interfaces, we realize that it could be made easier, especially for bindings, to write an Efl.Model that proxy another one without having to necessarily implement the entire logic of propagating event and checking if the property we are getting request for is actually handle by our own Efl.Model. To simplify this, I introduce this class that allow to set new callback for each property you want to handle on your object. Reviewed-by: Xavi Artigas Differential Revision: https://phab.enlightenment.org/D7487 --- src/Makefile_Ecore.am | 4 +- src/lib/ecore/Ecore_Eo.h | 1 + src/lib/ecore/efl_model_view.c | 569 ++++++++++++++++++++++++++++++++ src/lib/ecore/efl_model_view.eo | 104 ++++++ src/lib/ecore/meson.build | 4 +- 5 files changed, 680 insertions(+), 2 deletions(-) create mode 100644 src/lib/ecore/efl_model_view.c create mode 100644 src/lib/ecore/efl_model_view.eo diff --git a/src/Makefile_Ecore.am b/src/Makefile_Ecore.am index ff23d05680..38aeab1f6c 100644 --- a/src/Makefile_Ecore.am +++ b/src/Makefile_Ecore.am @@ -49,7 +49,8 @@ ecore_eolian_files_public = \ lib/ecore/efl_model_composite_boolean_children.eo \ lib/ecore/efl_model_composite_selection.eo \ lib/ecore/efl_model_composite_selection_children.eo \ - lib/ecore/efl_model_composite.eo + lib/ecore/efl_model_composite.eo \ + lib/ecore/efl_model_view.eo ecore_eolian_files = \ $(ecore_eolian_files_legacy) \ @@ -129,6 +130,7 @@ lib/ecore/efl_model_composite_selection.c \ lib/ecore/efl_model_composite_private.h \ lib/ecore/efl_model_accessor_view.c \ lib/ecore/efl_model_accessor_view_private.h \ +lib/ecore/efl_model_view.c \ lib/ecore/efl_linear_interpolator.c \ lib/ecore/efl_accelerate_interpolator.c \ lib/ecore/efl_decelerate_interpolator.c \ diff --git a/src/lib/ecore/Ecore_Eo.h b/src/lib/ecore/Ecore_Eo.h index a070cd1b60..f8d8626848 100644 --- a/src/lib/ecore/Ecore_Eo.h +++ b/src/lib/ecore/Ecore_Eo.h @@ -118,6 +118,7 @@ EAPI Eo *efl_main_loop_get(void); #include "efl_model_composite_boolean_children.eo.h" #include "efl_model_composite_selection.eo.h" #include "efl_model_composite_selection_children.eo.h" +#include "efl_model_view.eo.h" /** * @} diff --git a/src/lib/ecore/efl_model_view.c b/src/lib/ecore/efl_model_view.c new file mode 100644 index 0000000000..da9b672d5c --- /dev/null +++ b/src/lib/ecore/efl_model_view.c @@ -0,0 +1,569 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#include "ecore_internal.h" + +#include "efl_model_composite_private.h" + +typedef struct _Efl_Model_View_Data Efl_Model_View_Data; +typedef struct _Efl_Model_View_Bind Efl_Model_View_Bind; +typedef struct _Efl_Model_View_Logic Efl_Model_View_Logic; +typedef struct _Efl_Model_View_Property_Ref Efl_Model_View_Property_Ref; + +struct _Efl_Model_View_Data +{ + // FIXME: If parent is set, always access parent... recursively? + Efl_Model_View_Data *parent; + + Eina_Hash *bound; // Stringhash of Efl_Model_View_Bind + Eina_Hash *logics; // Stringhash of Efl_Model_View_Logic + + Eina_Hash *deduplication; // Stringhash of Efl_Model_View_Property_Ref + + struct { + Eina_Bool property_changed : 1; + Eina_Bool child_added : 1; + Eina_Bool child_removed : 1; + } propagating; // Boolean to prevent reentrance event emission on the same object + Eina_Bool finalized : 1; + Eina_Bool children_bind : 1; // Define if child object should be automatically binded +}; + +struct _Efl_Model_View_Bind +{ + Eina_Stringshare *source; + Eina_List *destinations; +}; + +struct _Efl_Model_View_Logic +{ + struct { + EflModelViewPropertyGet fct; + Eina_Free_Cb free_cb; + void *data; + } get; + struct { + EflModelViewPropertySet fct; + Eina_Free_Cb free_cb; + void *data; + } set; + + Efl_Object *object; + Eina_List *sources; + Eina_Stringshare *property; +}; + +struct _Efl_Model_View_Property_Ref +{ + EINA_REFCOUNT; + Eina_Stringshare *property; +}; + +static void +_ref_free(void *data) +{ + Efl_Model_View_Property_Ref *r = data; + + eina_stringshare_del(r->property); + free(r); +} + +static void +_ref_add(Efl_Model_View_Data *pd, Eina_Stringshare *property) +{ + Efl_Model_View_Property_Ref *r; + + r = eina_hash_find(pd->deduplication, property); + if (!r) + { + r = calloc(1, sizeof (Efl_Model_View_Property_Ref)); + if (!r) return ; + r->property = eina_stringshare_ref(property); + + eina_hash_direct_add(pd->deduplication, r->property, r); + } + + EINA_REFCOUNT_REF(r); +} + +static void +_ref_del(Efl_Model_View_Data *pd, Eina_Stringshare *property) +{ + Efl_Model_View_Property_Ref *r; + + r = eina_hash_find(pd->deduplication, property); + if (!r) return ; + + EINA_REFCOUNT_UNREF(r) + eina_hash_del(pd->deduplication, property, r); +} + +static void +_logic_free(void *data) +{ + Efl_Model_View_Logic *logic = data; + Eina_Stringshare *source; + + logic->get.free_cb(logic->get.data); + logic->set.free_cb(logic->set.data); + EINA_LIST_FREE(logic->sources, source) + { + efl_model_view_property_unbind(logic->object, source, logic->property); + eina_stringshare_del(source); + } + eina_stringshare_del(logic->property); + free(logic); +} + +static Eina_Error +_efl_model_view_property_logic_add(Eo *obj, Efl_Model_View_Data *pd, + const char *property, + void *get_data, EflModelViewPropertyGet get, Eina_Free_Cb get_free_cb, + void *set_data, EflModelViewPropertySet set, Eina_Free_Cb set_free_cb, + Eina_Iterator *bound) +{ + Efl_Model_View_Logic *logic; + Eina_Stringshare *prop; + const char *source; + + prop = eina_stringshare_add(property); + + if (eina_hash_find(pd->logics, prop)) + { + eina_stringshare_del(prop); + return EFL_MODEL_ERROR_INCORRECT_VALUE; + } + + logic = calloc(1, sizeof (Efl_Model_View_Logic)); + if (!logic) return ENOMEM; + + logic->object = obj; + logic->property = prop; + logic->get.fct = get; + logic->get.free_cb = get_free_cb; + logic->get.data = get_data; + logic->set.fct = set; + logic->set.free_cb = set_free_cb; + logic->set.data = set_data; + + eina_hash_direct_add(pd->logics, prop, logic); + + EINA_ITERATOR_FOREACH(bound, source) + { + logic->sources = eina_list_append(logic->sources, eina_stringshare_add(source)); + efl_model_view_property_bind(obj, source, property); + } + + return 0; +} + +static Eina_Error +_efl_model_view_property_logic_del(Eo *obj, Efl_Model_View_Data *pd, + const char *property) +{ + Efl_Model_View_Logic *logic; + + logic = eina_hash_find(pd->logics, property); + if (!logic) return EFL_MODEL_ERROR_INCORRECT_VALUE; + eina_hash_del(pd->logics, property, logic); + return 0; +} + +static void +_efl_model_view_property_bind(Eo *obj, Efl_Model_View_Data *pd, + const char *source, const char *destination) +{ + Efl_Model_View_Bind *bind; + Eina_Stringshare *src; + Eina_Stringshare *dst; + + if (!source || !destination) return ; + + src = eina_stringshare_add(source); + bind = eina_hash_find(pd->bound, src); + if (!bind) + { + bind = calloc(1, sizeof (Efl_Model_View_Bind)); + if (!bind) goto on_error; + bind->source = eina_stringshare_ref(src); + + eina_hash_direct_add(pd->bound, bind->source, bind); + } + + dst = eina_stringshare_add(destination); + bind->destinations = eina_list_append(bind->destinations, dst); + _ref_add(pd, dst); + + on_error: + eina_stringshare_del(src); +} + +static void +_efl_model_view_property_unbind(Eo *obj, Efl_Model_View_Data *pd, + const char *source, const char *destination) +{ + Efl_Model_View_Bind *bind; + Eina_Stringshare *src; + Eina_Stringshare *dst; + Eina_Stringshare *cmp; + Eina_List *l; + + if (!source || !destination) return ; + src = eina_stringshare_add(source); + bind = eina_hash_find(pd->bound, src); + if (!bind) goto on_error; + + dst = eina_stringshare_add(destination); + + EINA_LIST_FOREACH(bind->destinations, l, cmp) + if (cmp == dst) + { + bind->destinations = eina_list_remove_list(bind->destinations, l); + break; + } + + if (!bind->destinations) + eina_hash_del(pd->bound, dst, bind); + + _ref_del(pd, dst); + eina_stringshare_del(dst); + + on_error: + eina_stringshare_del(src); +} + +static void +_bind_free(void *data) +{ + Efl_Model_View_Bind *bind = data; + Eina_Stringshare *dst; + + eina_stringshare_del(bind->source); + + EINA_LIST_FREE(bind->destinations, dst) + eina_stringshare_del(dst); + + free(bind); +} + +static Efl_Model_View_Bind * +_efl_model_view_property_bind_lookup(Efl_Model_View_Data *pd, Eina_Stringshare *src) +{ + Efl_Model_View_Bind *bind; + + bind = eina_hash_find(pd->bound, src); + if (!bind && pd->parent) return _efl_model_view_property_bind_lookup(pd->parent, src); + return bind; +} + +static void +_efl_model_view_property_changed(void *data, const Efl_Event *event) +{ + Efl_Model_View_Data *pd = data; + Efl_Model_Property_Event *ev = event->info; + Efl_Model_Property_Event nev = { 0 }; + const char *property; + Eina_Stringshare *src; + Eina_Array_Iterator iterator; + unsigned int i; + + if (pd->propagating.property_changed) return ; + pd->propagating.property_changed = EINA_TRUE; + + // Our strategy is to rebuild a new Property_Event and cancel the current one. + efl_event_callback_stop(event->object); + + nev.changed_properties = eina_array_new(1); + + EINA_ARRAY_ITER_NEXT(ev->changed_properties, i, property, iterator) + { + Efl_Model_View_Bind *bind; + + eina_array_push(nev.changed_properties, property); + + src = eina_stringshare_add(property); + bind = _efl_model_view_property_bind_lookup(pd, src); + if (bind) + { + Eina_Stringshare *dest; + Eina_List *l; + + EINA_LIST_FOREACH(bind->destinations, l, dest) + eina_array_push(nev.changed_properties, dest); + } + } + + efl_event_callback_call(event->object, EFL_MODEL_EVENT_PROPERTIES_CHANGED, &nev); + + eina_array_free(nev.changed_properties); + + pd->propagating.property_changed = EINA_FALSE; +} + +static void +_efl_model_view_children_bind_set(Eo *obj, Efl_Model_View_Data *pd, Eina_Bool enable) +{ + if (pd->finalized) return; + + pd->children_bind = enable; +} + +static Eina_Bool +_efl_model_view_children_bind_get(const Eo *obj, Efl_Model_View_Data *pd) +{ + return pd->children_bind; +} + +static void +_efl_model_view_parent_data(Efl_Model_View *child, Efl_Model_View_Data *ppd) +{ + Efl_Model_View_Data *cpd; + + cpd = efl_data_scope_get(child, EFL_MODEL_VIEW_CLASS); + cpd->parent = ppd; + cpd->propagating = ppd->propagating; +} + +static Efl_Model_View * +_efl_model_view_child_lookup(Efl_Model_View_Data *pd, Efl_Object *parent, Efl_Model *view) +{ + Efl_Model_View *co; + + co = efl_key_wref_get(view, "_efl.model_view"); + if (co) return co; + + co = efl_add(EFL_MODEL_VIEW_CLASS, parent, + efl_ui_view_model_set(efl_added, view), + _efl_model_view_parent_data(efl_added, pd)); + if (!co) return NULL; + + efl_key_wref_set(view, "_efl.model_view", co); + + return co; +} + +static void +_efl_model_view_child_added(void *data, const Efl_Event *event) +{ + Efl_Model_Children_Event *ev = event->info; + Efl_Model_Children_Event nevt = { 0 }; + Efl_Model_View_Data *pd = data; + Efl_Model_View *co; + + if (pd->propagating.child_added) return ; + if (!pd->children_bind) return; + if (!ev->child) return; + + pd->propagating.child_added = EINA_TRUE; + + // Our strategy is to rebuild a new Child_Add and cancel the current one. + efl_event_callback_stop(event->object); + + co = _efl_model_view_child_lookup(pd, event->object, ev->child); + if (!co) return; + + nevt.index = ev->index; + nevt.child = co; + + efl_event_callback_call(event->object, EFL_MODEL_EVENT_CHILD_ADDED, &nevt); + + pd->propagating.child_added = EINA_FALSE; +} + +static void +_efl_model_view_child_removed(void *data, const Efl_Event *event) +{ + Efl_Model_Children_Event *ev = event->info; + Efl_Model_Children_Event nevt = { 0 }; + Efl_Model_View_Data *pd = data; + Efl_Model_View *co; + + if (pd->propagating.child_removed) return ; + if (!pd->children_bind) return; + if (!ev->child) return; + + pd->propagating.child_removed = EINA_TRUE; + + // Our strategy is to rebuild a new Child_Add and cancel the current one. + efl_event_callback_stop(event->object); + + co = _efl_model_view_child_lookup(pd, event->object, ev->child); + if (!co) return; + + nevt.index = ev->index; + nevt.child = co; + + efl_event_callback_call(event->object, EFL_MODEL_EVENT_CHILD_REMOVED, &nevt); + + // The object is being destroyed, there is no point in us keeping the ModelView proxy alive. + efl_del(co); + + pd->propagating.child_removed = EINA_FALSE; +} + +EFL_CALLBACKS_ARRAY_DEFINE(efl_model_view_intercept, + { EFL_MODEL_EVENT_PROPERTIES_CHANGED, _efl_model_view_property_changed }, + { EFL_MODEL_EVENT_CHILD_ADDED, _efl_model_view_child_added }, + { EFL_MODEL_EVENT_CHILD_REMOVED, _efl_model_view_child_removed }) + +static Efl_Object * +_efl_model_view_efl_object_constructor(Eo *obj, Efl_Model_View_Data *pd) +{ + obj = efl_constructor(efl_super(obj, EFL_MODEL_VIEW_CLASS)); + + pd->children_bind = EINA_TRUE; + pd->bound = eina_hash_stringshared_new(_bind_free); + pd->logics = eina_hash_stringshared_new(_logic_free); + pd->deduplication = eina_hash_stringshared_new(_ref_free); + + efl_event_callback_array_priority_add(obj, efl_model_view_intercept(), EFL_CALLBACK_PRIORITY_BEFORE, pd); + + return obj; +} + +static Efl_Object * +_efl_model_view_efl_object_finalize(Eo *obj, Efl_Model_View_Data *pd) +{ + pd->finalized = EINA_TRUE; + + return efl_finalize(efl_super(obj, EFL_MODEL_VIEW_CLASS)); +} + +static void +_efl_model_view_efl_object_destructor(Eo *obj, Efl_Model_View_Data *pd) +{ + efl_event_callback_array_del(obj, efl_model_view_intercept(), pd); + + eina_hash_free(pd->bound); + pd->bound = NULL; + + eina_hash_free(pd->logics); + pd->logics = NULL; + + efl_destructor(efl_super(obj, EFL_MODEL_VIEW_CLASS)); +} + +static Efl_Model_View_Logic * +_efl_model_view_property_logic_lookup(Efl_Model_View_Data *pd, Eina_Stringshare *property) +{ + Efl_Model_View_Logic *logic; + + if (!pd) return NULL; + logic = eina_hash_find(pd->logics, property); + if (!logic) return _efl_model_view_property_logic_lookup(pd->parent, property); + return logic; +} + +static Eina_Future * +_efl_model_view_efl_model_property_set(Eo *obj, Efl_Model_View_Data *pd, + const char *property, Eina_Value *value) +{ + Efl_Model_View_Logic *logic; + Eina_Stringshare *prop; + Eina_Future *f; + + prop = eina_stringshare_add(property); + logic = _efl_model_view_property_logic_lookup(pd, prop); + if (logic) + f = logic->set.fct(logic->get.data, obj, prop, value); + else + f = efl_model_property_set(efl_super(obj, EFL_MODEL_VIEW_CLASS), property, value); + + eina_stringshare_del(prop); + return f; +} + +static Eina_Value * +_efl_model_view_efl_model_property_get(const Eo *obj, Efl_Model_View_Data *pd, + const char *property) +{ + Efl_Model_View_Logic *logic; + Eina_Stringshare *prop; + Eina_Value *r; + + prop = eina_stringshare_add(property); + logic = _efl_model_view_property_logic_lookup(pd, prop); + if (logic) + r = logic->get.fct(logic->get.data, obj, prop); + else + r = efl_model_property_get(efl_super(obj, EFL_MODEL_VIEW_CLASS), property); + + eina_stringshare_del(prop); + return r; +} + +static Eina_Iterator * +_efl_model_view_efl_model_properties_get(const Eo *obj, Efl_Model_View_Data *pd) +{ + EFL_MODEL_COMPOSITE_PROPERTIES_SUPER(props, obj, EFL_MODEL_VIEW_CLASS, + eina_hash_iterator_key_new(pd->deduplication)); + + return props; +} + +typedef struct _Efl_Model_View_Slice_Request Efl_Model_View_Slice_Request; +struct _Efl_Model_View_Slice_Request +{ + Efl_Model_View_Data *pd; + unsigned int start; +}; + +static Eina_Value +_efl_model_view_slice_then(Eo *o, void *data, const Eina_Value v) +{ + Efl_Model_View_Slice_Request *req = data; + Eo *target; + Eina_Value r = EINA_VALUE_EMPTY; + unsigned int i, len; + + eina_value_array_setup(&r, EINA_VALUE_TYPE_OBJECT, 4); + + EINA_VALUE_ARRAY_FOREACH(&v, len, i, target) + { + Eo *composite; + + composite = _efl_model_view_child_lookup(req->pd, o, target); + eina_value_array_append(&r, composite); + } + + return r; +} + +static void +_efl_model_view_slice_clean(Eo *o EINA_UNUSED, void *data, const Eina_Future *dead_future EINA_UNUSED) +{ + free(data); +} + +static Eina_Future * +_efl_model_view_efl_model_children_slice_get(Eo *obj, Efl_Model_View_Data *pd, + unsigned int start, unsigned int count) +{ + Efl_Model_View_Slice_Request *req; + Eina_Future *f; + + f = efl_model_children_slice_get(efl_super(obj, EFL_MODEL_VIEW_CLASS), start, count); + + req = malloc(sizeof (Efl_Model_View_Slice_Request)); + if (!req) + { + eina_future_cancel(f); + return efl_loop_future_rejected(obj, ENOMEM); + } + + req->pd = pd; + req->start = start; + + return efl_future_then(obj, f, .success_type = EINA_VALUE_TYPE_ARRAY, + .success = _efl_model_view_slice_then, + .free = _efl_model_view_slice_clean, + .data = req); +} + +#include "efl_model_view.eo.c" diff --git a/src/lib/ecore/efl_model_view.eo b/src/lib/ecore/efl_model_view.eo new file mode 100644 index 0000000000..244d8f4a75 --- /dev/null +++ b/src/lib/ecore/efl_model_view.eo @@ -0,0 +1,104 @@ +function EflModelViewPropertyGet { + [[Function called when a property is get.]] + params { + @in model_view: const(Efl.Model_View); [[The ModelView object the @.property.get is issued on.]] + @in property: stringshare; [[The property name the @.property.get is issued on.]] + } + return: any_value_ptr; [[The property value.]] +}; + +function EflModelViewPropertySet { + [[Function called when a property is set.]] + params { + @in model_view: Efl.Model_View; [[The ModelView object the @.property.set is issued on.]] + @in property: stringshare; [[The property name the @.property.set is issued on.]] + @in value: any_value_ptr @owned; [[The new value to set.]] + } + return: future; [[The value that was finally set.]] +}; + +class Efl.Model_View (Efl.Model_Composite) +{ + [[Efl model providing helpers for custom properties used when linking a model to a view and you need to generate/adapt values for display. + + There is two ways to use this class, you can either inherit from it and have a custom constructor for example. Or you can just instantiate + it and manually define your property on it via callbacks. + ]] + methods { + property_logic_add { + [[Add callbacks that will be triggered when someone ask for the specified property name when getting or setting a property. + + A get or set should at least be provided for this call to succeed. + + See @.property_logic_del + ]] + params { + property: string; [[The property to bind on to.]] + get: EflModelViewPropertyGet; [[Define the get callback called when the @Efl.Model.property.get is called with the above property name.]] + set: EflModelViewPropertySet; [[Define the set callback called when the @Efl.Model.property.set is called with the above property name.]] + binded: iterator; [[Iterator of property name to bind with this defined property see @.property_bind.]] + } + return: Eina.Error; + } + property_logic_del { + [[Delete previously added callbacks that were triggered when someone asked for the specified property name when getting or setting a property. + + A get or set should at least be provided for this call to succeed. + + See @.property_logic_add + ]] + params { + property: string; [[The property to bind on to.]] + } + return: Eina.Error; + } + property_bind { + [[Automatically update the field for the event @[Efl.Model.properties,changed] to include property + that are impacted with change in a property from the composited model. + + The source doesn't have to be provided at this point by the composited model. + ]] + params { + @in source: string; [[Property name in the composited model.]] + @in destination: string; [[Property name in the @Efl.Model_View]] + } + } + property_unbind { + [[Stop automatically updating the field for the event @[Efl.Model.properties,changed] to + include property that are impacted with change in a property from the + composited model. + ]] + params { + @in source: string; [[Property name in the composited model.]] + @in destination: string; [[Property name in the @Efl.Model_View]] + } + } + @property children_bind { + [[Define if we will intercept all childrens object reference and + bind them through the ModelView with the same property logic as this + one. Be careful of recursivity. + + This can only be applied at construction time.]] + get { + [[Get the state of the automatic binding of children object.]] + } + set { + [[Set the state of the automatic binding of children object.]] + } + values { + enable: bool; [[Do you automatically bind children. Default to true.]] + } + } + } + implements { + Efl.Object.constructor; + Efl.Object.finalize; + Efl.Object.destructor; + Efl.Model.children_slice_get; + Efl.Model.properties { get; } + Efl.Model.property { set; get; } + } + constructors { + Efl.Model_View.children_bind; + } +} diff --git a/src/lib/ecore/meson.build b/src/lib/ecore/meson.build index 803e8d94fd..ce19d045e1 100644 --- a/src/lib/ecore/meson.build +++ b/src/lib/ecore/meson.build @@ -75,7 +75,8 @@ pub_eo_files = [ 'efl_model_composite_boolean_children.eo', 'efl_model_composite_selection.eo', 'efl_model_composite_selection_children.eo', - 'efl_model_composite.eo' + 'efl_model_composite.eo', + 'efl_model_view.eo' ] foreach eo_file : pub_eo_files @@ -156,6 +157,7 @@ ecore_src = [ 'efl_model_composite_private.h', 'efl_model_accessor_view.c', 'efl_model_accessor_view_private.h', + 'efl_model_view.c', 'efl_linear_interpolator.c', 'efl_accelerate_interpolator.c', 'efl_decelerate_interpolator.c',