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',