forked from enlightenment/efl
503 lines
16 KiB
C
503 lines
16 KiB
C
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <Eina.h>
|
|
#include <Efl.h>
|
|
#include <Ecore.h>
|
|
|
|
#include "ecore_private.h"
|
|
#include "efl_composite_model.eo.h"
|
|
|
|
typedef struct _Efl_Composite_Model_Data Efl_Composite_Model_Data;
|
|
|
|
struct _Efl_Composite_Model_Data
|
|
{
|
|
EINA_RBTREE;
|
|
|
|
Efl_Composite_Model *self;
|
|
Efl_Model *source;
|
|
Eina_Rbtree *indexed;
|
|
|
|
unsigned int index;
|
|
|
|
Eina_Bool need_index : 1;
|
|
Eina_Bool set_index : 1;
|
|
Eina_Bool inserted : 1;
|
|
};
|
|
|
|
static Eina_Rbtree_Direction
|
|
_children_indexed_cmp(const Efl_Composite_Model_Data *left,
|
|
const Efl_Composite_Model_Data *right,
|
|
void *data EINA_UNUSED)
|
|
{
|
|
if (left->index < right->index)
|
|
return EINA_RBTREE_LEFT;
|
|
return EINA_RBTREE_RIGHT;
|
|
}
|
|
|
|
static int
|
|
_children_indexed_key(const Efl_Composite_Model_Data *node,
|
|
const int *key, int length EINA_UNUSED, void *data EINA_UNUSED)
|
|
{
|
|
if (node->index > (unsigned int) *key) return 1;
|
|
if (node->index < (unsigned int) *key) return -1;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
_mark_greater(Efl_Composite_Model_Data *root, Eina_Array *mark, const unsigned int upper)
|
|
{
|
|
if (!root) return ;
|
|
|
|
if (root->index > upper)
|
|
{
|
|
eina_array_push(mark, root);
|
|
_mark_greater((void*) EINA_RBTREE_GET(root)->son[0], mark, upper);
|
|
_mark_greater((void*) EINA_RBTREE_GET(root)->son[1], mark, upper);
|
|
}
|
|
else
|
|
{
|
|
_mark_greater((void*) EINA_RBTREE_GET(root)->son[0], mark, upper);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_efl_composite_model_efl_object_invalidate(Eo *obj, Efl_Composite_Model_Data *pd)
|
|
{
|
|
if (pd->inserted)
|
|
{
|
|
Eo *parent;
|
|
|
|
parent = efl_parent_get(obj);
|
|
if (efl_isa(parent, EFL_COMPOSITE_MODEL_CLASS))
|
|
{
|
|
Efl_Composite_Model_Data *ppd;
|
|
|
|
ppd = efl_data_scope_get(parent, EFL_COMPOSITE_MODEL_CLASS);
|
|
ppd->indexed = eina_rbtree_inline_remove(ppd->indexed, EINA_RBTREE_GET(pd),
|
|
EINA_RBTREE_CMP_NODE_CB(_children_indexed_cmp), NULL);
|
|
pd->inserted = EINA_FALSE;
|
|
}
|
|
else
|
|
{
|
|
ERR("Unexpected parent change during the life of object: %s this might lead to crash.", efl_debug_name_get(obj));
|
|
}
|
|
}
|
|
|
|
efl_invalidate(efl_super(obj, EFL_COMPOSITE_MODEL_CLASS));
|
|
}
|
|
|
|
static Efl_Object *
|
|
_efl_composite_model_efl_object_finalize(Eo *obj, Efl_Composite_Model_Data *pd)
|
|
{
|
|
Eo *parent;
|
|
|
|
if (pd->source == NULL)
|
|
{
|
|
ERR("Source of the composite model wasn't defined at construction time.");
|
|
return NULL;
|
|
}
|
|
|
|
pd->self = obj;
|
|
|
|
parent = efl_parent_get(obj);
|
|
if (efl_isa(parent, EFL_COMPOSITE_MODEL_CLASS) && !pd->inserted)
|
|
{
|
|
Efl_Composite_Model_Data *ppd = efl_data_scope_get(parent, EFL_COMPOSITE_MODEL_CLASS);
|
|
Efl_Composite_Model_Data *lookup;
|
|
|
|
lookup = (void*) eina_rbtree_inline_lookup(ppd->indexed, &pd->index, sizeof (int),
|
|
EINA_RBTREE_CMP_KEY_CB(_children_indexed_key), NULL);
|
|
if (lookup)
|
|
{
|
|
// There is already an object at this index, we should not
|
|
// build anything different than what exist. Returning existing one.
|
|
return lookup->self;
|
|
}
|
|
else
|
|
{
|
|
ppd->indexed = eina_rbtree_inline_insert(ppd->indexed, EINA_RBTREE_GET(pd),
|
|
EINA_RBTREE_CMP_NODE_CB(_children_indexed_cmp), NULL);
|
|
|
|
pd->inserted = EINA_TRUE;
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
static void
|
|
_efl_composite_model_index_set(Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd, unsigned int index)
|
|
{
|
|
if (pd->set_index || !pd->source)
|
|
return ;
|
|
pd->index = index;
|
|
pd->set_index = EINA_TRUE;
|
|
}
|
|
|
|
static unsigned int
|
|
_efl_composite_model_index_get(const Eo *obj, Efl_Composite_Model_Data *pd)
|
|
{
|
|
Eina_Value *fetch = NULL;
|
|
unsigned int r = 0xFFFFFFFF;
|
|
|
|
if (pd->set_index)
|
|
return pd->index;
|
|
if (pd->need_index)
|
|
return 0xFFFFFFFF;
|
|
|
|
fetch = efl_model_property_get(obj, EFL_COMPOSITE_MODEL_CHILD_INDEX);
|
|
if (!eina_value_uint_convert(fetch, &r))
|
|
return 0xFFFFFFFF;
|
|
eina_value_free(fetch);
|
|
|
|
return r;
|
|
}
|
|
|
|
static void
|
|
_efl_composite_model_child_event(Efl_Composite_Model_Data *pd,
|
|
const Efl_Model_Children_Event *ev,
|
|
const Efl_Event_Description *description)
|
|
{
|
|
Efl_Composite_Model_Data *cpd;
|
|
Efl_Model_Children_Event cev = { 0 };
|
|
Eina_Array mark;
|
|
Eina_Array_Iterator iterator;
|
|
unsigned int i;
|
|
|
|
cev.index = ev->index;
|
|
if (ev->child)
|
|
{
|
|
cev.child = _efl_composite_lookup(efl_class_get(pd->self),
|
|
pd->self, ev->child, ev->index);
|
|
}
|
|
else
|
|
{
|
|
cpd = (void*) eina_rbtree_inline_lookup(pd->indexed, &cev.index, sizeof (unsigned int),
|
|
EINA_RBTREE_CMP_KEY_CB(_children_indexed_key), NULL);
|
|
if (cpd) cev.child = efl_ref(cpd->self);
|
|
}
|
|
|
|
if (cev.child && description == EFL_MODEL_EVENT_CHILD_REMOVED)
|
|
{
|
|
cpd = efl_data_scope_get(cev.child, EFL_COMPOSITE_MODEL_CLASS);
|
|
|
|
// Remove child from lookup tree if it exist before triggering anything further
|
|
pd->indexed = eina_rbtree_inline_remove(pd->indexed, EINA_RBTREE_GET(cpd),
|
|
EINA_RBTREE_CMP_NODE_CB(_children_indexed_cmp), NULL);
|
|
cpd->inserted = EINA_FALSE;
|
|
efl_replace(&cpd->source, NULL);
|
|
}
|
|
|
|
// Update all index above this one if necessaryy
|
|
eina_array_step_set(&mark, sizeof (Eina_Array), 8);
|
|
_mark_greater((void*) pd->indexed, &mark, cev.index);
|
|
|
|
// Correct index of the object stored that need to
|
|
// There is no need to remove and reinsert them as their relative order will not change.
|
|
EINA_ARRAY_ITER_NEXT(&mark, i, cpd, iterator)
|
|
{
|
|
if (description == EFL_MODEL_EVENT_CHILD_REMOVED) cpd->index--;
|
|
else cpd->index++;
|
|
|
|
efl_ref(cpd->self);
|
|
}
|
|
|
|
efl_event_callback_call(pd->self, description, &cev);
|
|
|
|
// Notify of the index change only after notifying of the removal top avoid overlap
|
|
EINA_ARRAY_ITER_NEXT(&mark, i, cpd, iterator)
|
|
{
|
|
efl_model_properties_changed(cpd->self, EFL_COMPOSITE_MODEL_CHILD_INDEX);
|
|
efl_unref(cpd->self);
|
|
}
|
|
eina_array_flush(&mark);
|
|
|
|
efl_unref(cev.child);
|
|
}
|
|
|
|
static void
|
|
_efl_composite_model_child_added(void *data, const Efl_Event *event)
|
|
{
|
|
Efl_Composite_Model_Data *pd = data;
|
|
Efl_Model_Children_Event *ev = event->info;
|
|
|
|
_efl_composite_model_child_event(pd, ev, EFL_MODEL_EVENT_CHILD_ADDED);
|
|
}
|
|
|
|
static void
|
|
_efl_composite_model_child_removed(void *data, const Efl_Event *event)
|
|
{
|
|
Efl_Composite_Model_Data *pd = data;
|
|
Efl_Model_Children_Event *ev = event->info;
|
|
|
|
_efl_composite_model_child_event(pd, ev, EFL_MODEL_EVENT_CHILD_REMOVED);
|
|
}
|
|
|
|
EFL_CALLBACKS_ARRAY_DEFINE(composite_callbacks,
|
|
{ EFL_MODEL_EVENT_CHILD_ADDED, _efl_composite_model_child_added },
|
|
{ EFL_MODEL_EVENT_CHILD_REMOVED, _efl_composite_model_child_removed });
|
|
|
|
static void
|
|
_efl_composite_model_efl_ui_view_model_set(Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd, Efl_Model *model)
|
|
{
|
|
Eina_Iterator *properties;
|
|
const char *property;
|
|
|
|
if (pd->source != NULL)
|
|
{
|
|
ERR("Source already set for composite model. It can only be set once.");
|
|
return ;
|
|
}
|
|
pd->source = efl_ref(model);
|
|
|
|
efl_event_callback_array_add(model, composite_callbacks(), pd);
|
|
efl_event_callback_forwarder_priority_add(model, EFL_MODEL_EVENT_CHILDREN_COUNT_CHANGED, EFL_CALLBACK_PRIORITY_BEFORE, obj);
|
|
efl_event_callback_forwarder_priority_add(model, EFL_MODEL_EVENT_PROPERTIES_CHANGED, EFL_CALLBACK_PRIORITY_BEFORE, obj);
|
|
|
|
pd->need_index = EINA_TRUE;
|
|
|
|
properties = efl_model_properties_get(pd->source);
|
|
EINA_ITERATOR_FOREACH(properties, property)
|
|
{
|
|
if (eina_streq(property, EFL_COMPOSITE_MODEL_CHILD_INDEX))
|
|
{
|
|
pd->need_index = EINA_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
eina_iterator_free(properties);
|
|
}
|
|
|
|
static Efl_Model *
|
|
_efl_composite_model_efl_ui_view_model_get(const Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd)
|
|
{
|
|
return pd->source;
|
|
}
|
|
|
|
static Eina_Future *
|
|
_efl_composite_model_efl_model_property_set(Eo *obj, Efl_Composite_Model_Data *pd,
|
|
const char *property, Eina_Value *value)
|
|
{
|
|
if (pd->need_index && eina_streq(property, EFL_COMPOSITE_MODEL_CHILD_INDEX))
|
|
{
|
|
if (pd->set_index || !pd->source)
|
|
return efl_loop_future_rejected(obj, EFL_MODEL_ERROR_READ_ONLY);
|
|
if (!eina_value_uint_convert(value, &pd->index))
|
|
return efl_loop_future_rejected(obj, EFL_MODEL_ERROR_UNKNOWN);
|
|
pd->set_index = EINA_TRUE;
|
|
return efl_loop_future_resolved(obj, eina_value_uint_init(pd->index));
|
|
}
|
|
return efl_model_property_set(pd->source, property, value);
|
|
}
|
|
|
|
static Eina_Value *
|
|
_efl_composite_model_efl_model_property_get(const Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd,
|
|
const char *property)
|
|
{
|
|
Eina_Value *try;
|
|
if (pd->need_index && eina_streq(property, EFL_COMPOSITE_MODEL_CHILD_INDEX))
|
|
{
|
|
if (pd->set_index)
|
|
return eina_value_uint_new(pd->index);
|
|
return eina_value_error_new(EAGAIN);
|
|
}
|
|
try = efl_model_property_get(efl_super(obj, EFL_COMPOSITE_MODEL_CLASS), property);
|
|
if (eina_value_type_get(try) == EINA_VALUE_TYPE_ERROR)
|
|
{
|
|
Eina_Error err = EINA_ERROR_NOT_IMPLEMENTED;
|
|
|
|
if (eina_value_error_get(try, &err) && (err == EINA_ERROR_NOT_IMPLEMENTED))
|
|
{
|
|
eina_value_free(try);
|
|
return efl_model_property_get(pd->source, property);
|
|
}
|
|
}
|
|
return try;
|
|
}
|
|
|
|
static Eina_Iterator *
|
|
_efl_composite_model_efl_model_properties_get(const Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd)
|
|
{
|
|
if (pd->need_index)
|
|
{
|
|
static const char *composite_properties[] = {
|
|
EFL_COMPOSITE_MODEL_CHILD_INDEX
|
|
};
|
|
|
|
return eina_multi_iterator_new(efl_model_properties_get(pd->source),
|
|
EINA_C_ARRAY_ITERATOR_NEW(composite_properties));
|
|
}
|
|
return efl_model_properties_get(pd->source);
|
|
}
|
|
|
|
static unsigned int
|
|
_efl_composite_model_efl_model_children_count_get(const Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd)
|
|
{
|
|
return efl_model_children_count_get(pd->source);
|
|
}
|
|
|
|
typedef struct _Efl_Composite_Model_Slice_Request Efl_Composite_Model_Slice_Request;
|
|
struct _Efl_Composite_Model_Slice_Request
|
|
{
|
|
const Efl_Class *self;
|
|
Eo *parent;
|
|
unsigned int start;
|
|
unsigned int dummy_need;
|
|
};
|
|
|
|
static Eina_Value
|
|
_efl_composite_model_then(Eo *o EINA_UNUSED, void *data, const Eina_Value v)
|
|
{
|
|
Efl_Composite_Model_Slice_Request *req = data;
|
|
unsigned int i, len;
|
|
Eina_Value r = EINA_VALUE_EMPTY;
|
|
Eo *target = NULL;
|
|
|
|
eina_value_array_setup(&r, EINA_VALUE_TYPE_OBJECT, 4);
|
|
|
|
EINA_VALUE_ARRAY_FOREACH(&v, len, i, target)
|
|
{
|
|
Eo *composite;
|
|
|
|
// Fetch an existing composite model for this model or create a new one if none exist
|
|
composite = _efl_composite_lookup(req->self, req->parent, target, req->start + i);
|
|
if (!composite) continue;
|
|
|
|
eina_value_array_append(&r, composite);
|
|
// Dropping this scope reference
|
|
efl_unref(composite);
|
|
}
|
|
|
|
while (req->dummy_need)
|
|
{
|
|
Eo *dummy, *dummy_proxy;
|
|
|
|
// Create a dummy object and its proxy
|
|
dummy = efl_add(EFL_GENERIC_MODEL_CLASS, req->parent);
|
|
dummy_proxy = efl_add_ref(req->self, req->parent,
|
|
efl_ui_view_model_set(efl_added, dummy),
|
|
efl_composite_model_index_set(efl_added, req->start + i),
|
|
efl_loop_model_volatile_make(efl_added));
|
|
efl_parent_set(dummy, dummy_proxy);
|
|
|
|
eina_value_array_append(&r, dummy_proxy);
|
|
efl_unref(dummy_proxy);
|
|
|
|
req->dummy_need--;
|
|
i++;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static void
|
|
_efl_composite_model_clean(Eo *o EINA_UNUSED, void *data, const Eina_Future *dead_future EINA_UNUSED)
|
|
{
|
|
Efl_Composite_Model_Slice_Request *req = data;
|
|
|
|
efl_unref(req->parent);
|
|
free(data);
|
|
}
|
|
|
|
static Eina_Future *
|
|
_efl_composite_model_efl_model_children_slice_get(Eo *obj,
|
|
Efl_Composite_Model_Data *pd,
|
|
unsigned int start,
|
|
unsigned int count)
|
|
{
|
|
Efl_Composite_Model_Slice_Request *req;
|
|
Eina_Future *f;
|
|
unsigned int source_count, self_count;
|
|
unsigned int req_count;
|
|
|
|
source_count = efl_model_children_count_get(pd->source);
|
|
self_count = efl_model_children_count_get(obj);
|
|
|
|
if (start + count > source_count &&
|
|
start + count > self_count)
|
|
return efl_loop_future_rejected(obj, EFL_MODEL_ERROR_INCORRECT_VALUE);
|
|
|
|
if (start > source_count)
|
|
{
|
|
Eina_Value r = EINA_VALUE_EMPTY;
|
|
unsigned int i = 0;
|
|
|
|
eina_value_array_setup(&r, EINA_VALUE_TYPE_OBJECT, 4);
|
|
|
|
while (count)
|
|
{
|
|
Eo *dummy, *dummy_proxy;
|
|
|
|
// Create a dummy object and its proxy
|
|
dummy = efl_add(EFL_GENERIC_MODEL_CLASS, obj);
|
|
dummy_proxy = efl_add_ref(efl_class_get(obj), obj,
|
|
efl_ui_view_model_set(efl_added, dummy),
|
|
efl_composite_model_index_set(efl_added, start + i),
|
|
efl_loop_model_volatile_make(efl_added));
|
|
efl_parent_set(dummy, dummy_proxy);
|
|
|
|
eina_value_array_append(&r, dummy_proxy);
|
|
efl_unref(dummy_proxy);
|
|
|
|
count--;
|
|
i++;
|
|
}
|
|
|
|
return efl_loop_future_resolved(obj, r);
|
|
}
|
|
|
|
req_count = start + count > source_count ? source_count - start : count;
|
|
f = efl_model_children_slice_get(pd->source, start, req_count);
|
|
if (!f) return NULL;
|
|
|
|
req = malloc(sizeof (Efl_Composite_Model_Slice_Request));
|
|
if (!req) return efl_loop_future_rejected(obj, ENOMEM);
|
|
|
|
req->self = efl_class_get(obj);
|
|
req->parent = efl_ref(obj);
|
|
req->start = start;
|
|
if (start + count < source_count)
|
|
req->dummy_need = 0;
|
|
else
|
|
req->dummy_need = count - (source_count - start);
|
|
|
|
return efl_future_then(obj, f, .success_type = EINA_VALUE_TYPE_ARRAY,
|
|
.success = _efl_composite_model_then,
|
|
.free = _efl_composite_model_clean,
|
|
.data = req);
|
|
}
|
|
|
|
static Efl_Object *
|
|
_efl_composite_model_efl_model_child_add(Eo *obj EINA_UNUSED,
|
|
Efl_Composite_Model_Data *pd)
|
|
{
|
|
return efl_model_child_add(pd->source);
|
|
}
|
|
|
|
static void
|
|
_efl_composite_model_efl_model_child_del(Eo *obj EINA_UNUSED,
|
|
Efl_Composite_Model_Data *pd,
|
|
Efl_Object *child)
|
|
{
|
|
efl_model_child_del(pd->source, efl_ui_view_model_get(child));
|
|
}
|
|
|
|
static void
|
|
_efl_composite_model_efl_object_destructor(Eo *obj, Efl_Composite_Model_Data *pd)
|
|
{
|
|
if (pd->source)
|
|
{
|
|
efl_event_callback_array_del(pd->source, composite_callbacks(), pd);
|
|
efl_event_callback_forwarder_del(pd->source, EFL_MODEL_EVENT_CHILDREN_COUNT_CHANGED, obj);
|
|
efl_event_callback_forwarder_del(pd->source, EFL_MODEL_EVENT_PROPERTIES_CHANGED, obj);
|
|
|
|
efl_replace(&pd->source, NULL);
|
|
}
|
|
|
|
efl_destructor(efl_super(obj, EFL_COMPOSITE_MODEL_CLASS));
|
|
}
|
|
|
|
#include "efl_composite_model.eo.c"
|