ecore: add simple logic to generate synthetic string property assembling value of other properties.

I have choosen to use ${} as the property marker, but I am open to other suggestion. Let
me know if you have other suggestion.

This patch also fix recursion on properties changed to properly explore parents dependencies,
avoid infinite loop and emit properties changed by added property logic or property text
on the Efl.ViewModel.

Reviewed-by: Lauro Neto <Lauro Moura <lauromoura@expertisesolutions.com.br>>
Differential Revision: https://phab.enlightenment.org/D8755
This commit is contained in:
Cedric BAIL 2019-04-28 10:59:57 -07:00
parent a86a0931f1
commit c84c421267
2 changed files with 326 additions and 17 deletions

View File

@ -13,6 +13,7 @@
typedef struct _Efl_View_Model_Data Efl_View_Model_Data;
typedef struct _Efl_View_Model_Bind Efl_View_Model_Bind;
typedef struct _Efl_View_Model_Text Efl_View_Model_Text;
typedef struct _Efl_View_Model_Logic Efl_View_Model_Logic;
typedef struct _Efl_View_Model_Property_Ref Efl_View_Model_Property_Ref;
@ -23,6 +24,7 @@ struct _Efl_View_Model_Data
Eina_Hash *bound; // Stringhash of Efl_View_Model_Bind
Eina_Hash *logics; // Stringhash of Efl_View_Model_Logic
Eina_Hash *texts; // Stringhash of Efl_View_Model_Text
Eina_Hash *deduplication; // Stringhash of Efl_View_Model_Property_Ref
@ -35,6 +37,15 @@ struct _Efl_View_Model_Data
Eina_Bool children_bind : 1; // Define if child object should be automatically binded
};
struct _Efl_View_Model_Text
{
Eina_Stringshare *name;
Eina_Stringshare *definition;
Eina_Stringshare *not_ready;
Eina_Stringshare *on_error;
Efl_Model *self;
};
struct _Efl_View_Model_Bind
{
Eina_Stringshare *source;
@ -192,6 +203,163 @@ _efl_view_model_property_logic_del(Eo *obj EINA_UNUSED, Efl_View_Model_Data *pd,
return 0;
}
static int
_lookup_next_token(const char *definition,
Eina_Slstr **text,
Eina_Slstr **property)
{
const char *lookup_text;
const char *lookup_property;
if (!definition) return 0;
*text = NULL;
*property = NULL;
lookup_text = strstr(definition, "${");
if (!lookup_text) goto on_error;
lookup_text += 2;
lookup_property = strchr(lookup_text, '}');
if (!lookup_property) goto on_error;
*text = eina_slstr_copy_new_length(definition, lookup_text - definition - 2);
*property = eina_slstr_copy_new_length(lookup_text, lookup_property - lookup_text);
return lookup_property + 1 - definition;
on_error:
if (strlen(definition) == 0) return 0;
*text = eina_slstr_copy_new(definition);
return strlen(definition);
}
static Eina_Error
_efl_view_model_property_string_add(Eo *obj, Efl_View_Model_Data *pd,
const char *name,
const char *definition,
const char *not_ready,
const char *on_error)
{
Efl_View_Model_Text *text;
Eina_Stringshare *sn;
Eina_Slstr *st = NULL;
Eina_Slstr *sp = NULL;
int lookup;
Eina_Error err = ENOMEM;
if (!name || !definition) return EFL_MODEL_ERROR_INCORRECT_VALUE;
if (!strlen(name)) return EFL_MODEL_ERROR_INCORRECT_VALUE;
if (!strlen(definition)) return EFL_MODEL_ERROR_INCORRECT_VALUE;
sn = eina_stringshare_add(name);
// Lookup if there is an existing property defined and undo it first
text = eina_hash_find(pd->texts, sn);
if (text) efl_view_model_property_string_del(obj, sn);
text = calloc(1, sizeof (Efl_View_Model_Text));
if (!text) goto on_error;
err = EFL_MODEL_ERROR_INCORRECT_VALUE;
text->name = eina_stringshare_add(name);
text->definition = eina_stringshare_add(definition);
text->not_ready = not_ready ? eina_stringshare_add(not_ready) : NULL;
text->on_error = on_error ? eina_stringshare_add(on_error) : NULL;
text->self = obj;
for (lookup = _lookup_next_token(definition, &st, &sp);
lookup;
definition += lookup, lookup = _lookup_next_token(definition, &st, &sp))
{
if (sp) efl_view_model_property_bind(obj, sp, name);
}
for (lookup = _lookup_next_token(not_ready, &st, &sp);
lookup;
not_ready += lookup, lookup = _lookup_next_token(not_ready, &st, &sp))
{
if (sp) efl_view_model_property_bind(obj, sp, name);
}
for (lookup = _lookup_next_token(on_error, &st, &sp);
lookup;
on_error += lookup, lookup = _lookup_next_token(on_error, &st, &sp))
{
if (sp) efl_view_model_property_bind(obj, sp, name);
}
eina_hash_direct_add(pd->texts, text->name, text);
return 0;
on_error:
eina_stringshare_del(sn);
free(text);
return err;
}
static void
_text_free(void *data)
{
Efl_View_Model_Text *text = data;
Eina_Stringshare *st;
Eina_Stringshare *sp;
int lookup;
const char *tmp;
tmp = text->definition;
for (lookup = _lookup_next_token(tmp, &st, &sp);
lookup;
tmp += lookup, lookup = _lookup_next_token(tmp, &st, &sp))
{
if (sp) efl_view_model_property_unbind(text->self, sp, text->name);
}
tmp = text->not_ready;
for (lookup = _lookup_next_token(tmp, &st, &sp);
lookup;
tmp += lookup, lookup = _lookup_next_token(tmp, &st, &sp))
{
if (sp) efl_view_model_property_unbind(text->self, sp, text->name);
}
tmp = text->on_error;
for (lookup = _lookup_next_token(tmp, &st, &sp);
lookup;
tmp += lookup, lookup = _lookup_next_token(tmp, &st, &sp))
{
if (sp) efl_view_model_property_unbind(text->self, sp, text->name);
}
eina_stringshare_del(text->name);
eina_stringshare_del(text->not_ready);
eina_stringshare_del(text->on_error);
free(text);
}
static Eina_Error
_efl_view_model_property_string_del(Eo *obj EINA_UNUSED,
Efl_View_Model_Data *pd,
const char *name)
{
Efl_View_Model_Text *text;
Eina_Stringshare *sn;
Eina_Error err = EFL_MODEL_ERROR_INCORRECT_VALUE;
if (!name) return EFL_MODEL_ERROR_INCORRECT_VALUE;
sn = eina_stringshare_add(name);
text = eina_hash_find(pd->texts, sn);
if (!text) goto on_error;
eina_hash_del(pd->texts, sn, text);
err = 0;
on_error:
eina_stringshare_del(sn);
return err;
}
static void
_efl_view_model_property_bind(Eo *obj EINA_UNUSED, Efl_View_Model_Data *pd,
const char *source, const char *destination)
@ -269,14 +437,36 @@ _bind_free(void *data)
free(bind);
}
static Efl_View_Model_Bind *
_efl_view_model_property_bind_lookup(Efl_View_Model_Data *pd, Eina_Stringshare *src)
static void
_efl_view_model_property_bind_lookup(Eina_Array *changed_properties,
Efl_View_Model_Data *pd,
Eina_Stringshare *src)
{
Efl_View_Model_Bind *bind;
if (!pd) return ;
bind = eina_hash_find(pd->bound, src);
if (!bind && pd->parent) return _efl_view_model_property_bind_lookup(pd->parent, src);
return bind;
if (bind)
{
Eina_Stringshare *dest;
Eina_List *l;
EINA_LIST_FOREACH(bind->destinations, l, dest)
{
// Check for duplicated entry first to avoid infinite recursion
Eina_Stringshare *dup = NULL;
Eina_Array_Iterator iterator;
unsigned int i;
EINA_ARRAY_ITER_NEXT(changed_properties, i, dup, iterator)
if (dup == dest) break;
if (dup == dest) continue ;
eina_array_push(changed_properties, dest);
_efl_view_model_property_bind_lookup(changed_properties, pd, dest);
}
}
_efl_view_model_property_bind_lookup(changed_properties, pd->parent, src);
}
static void
@ -300,20 +490,10 @@ _efl_view_model_property_changed(void *data, const Efl_Event *event)
EINA_ARRAY_ITER_NEXT(ev->changed_properties, i, property, iterator)
{
Efl_View_Model_Bind *bind;
eina_array_push(nev.changed_properties, property);
src = eina_stringshare_ref(property);
bind = _efl_view_model_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_view_model_property_bind_lookup(nev.changed_properties, pd, src);
}
efl_event_callback_call(event->object, EFL_MODEL_EVENT_PROPERTIES_CHANGED, &nev);
@ -433,6 +613,7 @@ _efl_view_model_efl_object_constructor(Eo *obj, Efl_View_Model_Data *pd)
pd->bound = eina_hash_stringshared_new(_bind_free);
pd->logics = eina_hash_stringshared_new(_logic_free);
pd->deduplication = eina_hash_stringshared_new(_ref_free);
pd->texts = eina_hash_stringshared_new(_text_free);
efl_event_callback_array_priority_add(obj, efl_view_model_intercept(), EFL_CALLBACK_PRIORITY_BEFORE, pd);
@ -458,6 +639,12 @@ _efl_view_model_efl_object_destructor(Eo *obj, Efl_View_Model_Data *pd)
eina_hash_free(pd->logics);
pd->logics = NULL;
eina_hash_free(pd->texts);
pd->texts = NULL;
eina_hash_free(pd->deduplication);
pd->deduplication = NULL;
efl_destructor(efl_super(obj, EFL_VIEW_MODEL_CLASS));
}
@ -485,12 +672,103 @@ _efl_view_model_efl_model_property_set(Eo *obj, Efl_View_Model_Data *pd,
if (logic)
f = logic->set.fct(logic->get.data, obj, prop, value);
else
f = efl_model_property_set(efl_super(obj, EFL_VIEW_MODEL_CLASS), property, value);
{
if (eina_hash_find(pd->texts, prop))
f = efl_loop_future_rejected(obj, EFL_MODEL_ERROR_READ_ONLY);
else
f = efl_model_property_set(efl_super(obj, EFL_VIEW_MODEL_CLASS), property, value);
}
eina_stringshare_del(prop);
return f;
}
static Eina_Value *
_efl_view_model_text_generate(const Eo *obj,
Eina_Strbuf *out,
Eina_Stringshare *pattern,
Eina_Bool stop_on_error)
{
Eina_Stringshare *st;
Eina_Stringshare *sp;
int lookup;
for (lookup = _lookup_next_token(pattern, &st, &sp);
lookup;
pattern += lookup, lookup = _lookup_next_token(pattern, &st, &sp))
{
Eina_Value *request;
char *sr;
eina_strbuf_append(out, st);
if (!sp) continue;
request = efl_model_property_get(obj, sp);
if (!request)
{
if (stop_on_error)
return eina_value_error_new(EFL_MODEL_ERROR_NOT_SUPPORTED);
eina_strbuf_append(out, "Unknown property");
continue;
}
if (eina_value_type_get(request) == EINA_VALUE_TYPE_ERROR && stop_on_error)
return request;
sr = eina_value_to_string(request);
eina_strbuf_append(out, sr);
free(sr);
eina_value_free(request);
}
return eina_value_string_new(eina_strbuf_string_get(out));
}
static Eina_Value *
_efl_view_model_text_property_get(const Eo *obj, Efl_View_Model_Data *pd, Eina_Stringshare *prop)
{
Efl_View_Model_Text *lookup;
Eina_Strbuf *buf;
Eina_Value *r;
Eina_Error err = 0;
if (!pd) return NULL;
lookup = eina_hash_find(pd->texts, prop);
// Lookup for property definition in the parent, but property value will be fetched on
// the child object doing the request.
if (!lookup) return _efl_view_model_text_property_get(obj, pd->parent, prop);
buf = eina_strbuf_new();
r = _efl_view_model_text_generate(obj, buf,
lookup->definition,
!!(lookup->on_error || lookup->not_ready));
if (eina_value_type_get(r) != EINA_VALUE_TYPE_ERROR)
goto done;
if (eina_value_error_get(r, &err) && err == EAGAIN && lookup->not_ready)
{
eina_strbuf_reset(buf);
eina_value_free(r);
r = _efl_view_model_text_generate(obj, buf, lookup->not_ready, !!lookup->on_error);
if (eina_value_type_get(r) != EINA_VALUE_TYPE_ERROR)
goto done;
}
if (lookup->on_error)
{
eina_strbuf_reset(buf);
eina_value_free(r);
r = _efl_view_model_text_generate(obj, buf, lookup->on_error, 0);
}
done:
eina_strbuf_free(buf);
return r;
}
static Eina_Value *
_efl_view_model_efl_model_property_get(const Eo *obj, Efl_View_Model_Data *pd,
const char *property)
@ -504,7 +782,10 @@ _efl_view_model_efl_model_property_get(const Eo *obj, Efl_View_Model_Data *pd,
if (logic)
r = logic->get.fct(logic->get.data, obj, prop);
else
r = efl_model_property_get(efl_super(obj, EFL_VIEW_MODEL_CLASS), property);
{
r = _efl_view_model_text_property_get(obj, pd, prop);
if (!r) r = efl_model_property_get(efl_super(obj, EFL_VIEW_MODEL_CLASS), property);
}
eina_stringshare_del(prop);
return r;

View File

@ -25,6 +25,34 @@ class @beta Efl.View_Model extends Efl.Composite_Model
it and manually define your property on it via callbacks.
]]
methods {
property_string_add {
[[Adds a synthetic string property, generated from a $definition string and other properties in the model.
The $definition string, similar to how $printf works, contains ${} placeholders that are replaced by the actual value of the property inside the placeholder tags when the synthetic property is retrieved.
For example, a numeric property $length might be strange to use as a label, since it will only display a number. However, a synthetic string can be generated with the definition "Length ${length}." which renders more nicely and does not require any more code by the user of the property.
$not_ready and $on_error strings can be given to be used when the data is not ready or there is some error, respectively. These strings do accept placeholder tags.
See @.property_string_del
]]
params {
name: string; [[The name for the new synthetic property.]]
definition: string; [[The definition string for the new synthetic property.]]
not_ready: string; [[The text to be used if any of the properties used in $definition is not ready yet. If set to $null, no check against EAGAIN will be done.]]
on_error: string; [[The text to be used if any of the properties used in $definition is in error. It takes precedence over $not_ready. If set to $null, no error checks are performed.]]
}
return: Eina.Error;
}
property_string_del {
[[Delete a synthetic property previously defined by @.property_string_add.
See @.property_string_add
]]
params {
name: string; [[The name of the synthetic property to delete.]]
}
return: Eina.Error;
}
property_logic_add {
[[Add callbacks that will be triggered when someone ask for the specified property name when getting or setting a property.