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 <xavierartigas@yahoo.es>
Differential Revision: https://phab.enlightenment.org/D7487
This commit is contained in:
Cedric BAIL 2018-11-28 16:03:33 -08:00 committed by Cedric BAIL
parent 07e60713fe
commit 29fcdd8a4b
5 changed files with 680 additions and 2 deletions

View File

@ -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 \

View File

@ -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"
/**
* @}

View File

@ -0,0 +1,569 @@
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <Efl.h>
#include <Eina.h>
#include <Eo.h>
#include <Ecore.h>
#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"

View File

@ -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<any_value_ptr>; [[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<string>; [[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;
}
}

View File

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