This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch edje-vector-intergration
in repository efl.
View the commit online.
commit b61568fdb94e6f73eaecffc89fba20f9f3c8172d
Author: [email protected] <[email protected]>
AuthorDate: Thu Apr 30 19:31:13 2026 -0600
edje_edit: add vector-tree and override editing API (Phase 8.2)
This commit introduces 10 new Edje_Edit public API symbols to enable
visual editors and round-trip tooling (like edje_decc) to manipulate
named Edje_Vg_Tree entries and per-node overrides end-to-end:
Tree directory (4 symbols):
edje_edit_vg_trees_list_get() — enumerate named trees
edje_edit_vg_tree_exists() — test tree presence
edje_edit_vg_tree_add() — create new empty tree
edje_edit_vg_tree_del() — remove tree (with in-use check)
Part-state bindings (4 symbols):
edje_edit_part_vector_tree_use_get() — read bound tree name
edje_edit_part_vector_tree_use_set() — bind tree to part-state
edje_edit_part_vector_overrides_list_get() — enumerate overrides
edje_edit_part_vector_override_{add,del}() — manage overrides
Node properties (2 symbols):
edje_edit_vector_node_color_class_{get,set}() — bind color classes
API naming: Uses "vg_tree" prefix to avoid collision with the pre-existing
unrelated edje_edit_vectors_list_get (which returns SVG image-dir entries).
BREAKING FIX: Expose _edje_file_coll_open and update _edje_vg_node_free/
_edje_vg_tree_free_fields to accept a free_strings flag. This fixes a
pre-existing stringshare corruption bug that was triggered when any caller
opened a compiled .edj and let Eet-loaded string dictionaries be freed:
The Eet descriptor decodes strings into Eina_Stringshare handles that
point into edf->free_strings dictionary. When free_strings==0 (Eet-managed),
calling eina_stringshare_del on these pointers corrupts the dictionary.
The new vg_tree_del implementation scans unloaded groups via _edje_file_coll_open
to check for in-use references; this would have crashed during file cleanup
when Edf_Free tried to release tree strings that were never stringshare'd.
Fix: Add Eina_Bool free_strings parameter to _edje_vg_node_free and
_edje_vg_tree_free_fields. Pass edf->free_strings from _edje_file_free to
prevent stringshare_del calls on Eet-dictionary strings.
Test coverage: 10 unit tests in edje_test_edit_vector.c covering all symbols,
round-trip save/load, and the memmove+tree_id-- cleanup path in vg_tree_del.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
src/lib/edje/Edje_Edit.h | 171 ++++++++++++++
src/lib/edje/edje_cache.c | 2 +-
src/lib/edje/edje_edit.c | 393 +++++++++++++++++++++++++++++++
src/lib/edje/edje_load.c | 3 +-
src/lib/edje/edje_private.h | 1 +
src/lib/edje/edje_vg_tree.c | 47 ++--
src/lib/edje/edje_vg_tree.h | 19 +-
src/tests/edje/edje_suite.c | 1 +
src/tests/edje/edje_suite.h | 1 +
src/tests/edje/edje_test_edit_vector.c | 406 +++++++++++++++++++++++++++++++++
src/tests/edje/meson.build | 1 +
11 files changed, 1019 insertions(+), 26 deletions(-)
diff --git a/src/lib/edje/Edje_Edit.h b/src/lib/edje/Edje_Edit.h
index 4de878fe43..90811611aa 100644
--- a/src/lib/edje/Edje_Edit.h
+++ b/src/lib/edje/Edje_Edit.h
@@ -8948,6 +8948,177 @@ EAPI extern Eina_Error EDJE_EDIT_ERROR_GROUP_REFERENCED;
EAPI extern Eina_Error EDJE_EDIT_ERROR_GROUP_DOES_NOT_EXIST;
+/******************************************************************************/
+/************************** VECTOR TREE API ******************************/
+/******************************************************************************/
+/**
+ * @name Vector Tree API
+ * Functions for manipulating the named Edje_Vg_Tree directory (Phase 8.2).
+ * This API operates on the toplevel @c vectors {} block, which holds named
+ * Edje_Vg_Tree entries. It is distinct from the SVG-file image-directory
+ * API (edje_edit_vector_del, edje_edit_vectors_list_get, etc.).
+ */ //@{
+
+/**
+ * @brief Returns the list of named vector-tree names in the file.
+ *
+ * Use edje_edit_string_list_free() to release the returned list.
+ *
+ * @param obj Object being edited.
+ *
+ * @return A list of Eina_Stringshare names, one per named Edje_Vg_Tree.
+ * @since 1.27
+ */
+EAPI Eina_List *edje_edit_vg_trees_list_get(Evas_Object *obj);
+
+/**
+ * @brief Tests whether a named vector tree exists in the file.
+ *
+ * @param obj Object being edited.
+ * @param name The tree name to look up.
+ *
+ * @return @c EINA_TRUE if a tree with that name is present, @c EINA_FALSE otherwise.
+ * @since 1.27
+ */
+EAPI Eina_Bool edje_edit_vg_tree_exists(Evas_Object *obj, const char *name);
+
+/**
+ * @brief Adds a new empty named vector tree to the file.
+ *
+ * Returns @c EINA_FALSE if a tree with that name already exists.
+ *
+ * @param obj Object being edited.
+ * @param name The name for the new tree.
+ *
+ * @return @c EINA_TRUE on success, @c EINA_FALSE otherwise.
+ * @since 1.27
+ */
+EAPI Eina_Bool edje_edit_vg_tree_add(Evas_Object *obj, const char *name);
+
+/**
+ * @brief Removes a named vector tree from the file.
+ *
+ * Refuses and returns @c EINA_FALSE if any description's tree_id references
+ * this tree (which would leave the file inconsistent).
+ *
+ * @param obj Object being edited.
+ * @param name The name of the tree to remove.
+ *
+ * @return @c EINA_TRUE on success, @c EINA_FALSE otherwise.
+ * @since 1.27
+ */
+EAPI Eina_Bool edje_edit_vg_tree_del(Evas_Object *obj, const char *name);
+
+/**
+ * @brief Gets the named vector tree bound to the given part-state.
+ *
+ * The returned string is a newly added Eina_Stringshare; release with
+ * edje_edit_string_free(). Returns NULL if no tree is bound (tree_id == -1)
+ * or if the part is not of type VECTOR.
+ *
+ * @param obj Object being edited.
+ * @param part Part name.
+ * @param state State name (without value).
+ * @param value State value.
+ *
+ * @return The tree name or NULL.
+ * @since 1.27
+ */
+EAPI const char *edje_edit_part_vector_tree_use_get(Evas_Object *obj, const char *part, const char *state, double value);
+
+/**
+ * @brief Binds a named vector tree to the given part-state.
+ *
+ * Passing NULL for @p tree_name clears the binding (sets tree_id to -1).
+ *
+ * @param obj Object being edited.
+ * @param part Part name.
+ * @param state State name (without value).
+ * @param value State value.
+ * @param tree_name The tree to bind, or NULL to clear.
+ *
+ * @return @c EINA_TRUE on success, @c EINA_FALSE otherwise.
+ * @since 1.27
+ */
+EAPI Eina_Bool edje_edit_part_vector_tree_use_set(Evas_Object *obj, const char *part, const char *state, double value, const char *tree_name);
+
+/**
+ * @brief Returns the list of override target names for a part-state.
+ *
+ * Use edje_edit_string_list_free() to release the returned list.
+ *
+ * @param obj Object being edited.
+ * @param part Part name.
+ * @param state State name (without value).
+ * @param value State value.
+ *
+ * @return A list of Eina_Stringshare target names.
+ * @since 1.27
+ */
+EAPI Eina_List *edje_edit_part_vector_overrides_list_get(Evas_Object *obj, const char *part, const char *state, double value);
+
+/**
+ * @brief Adds an override entry for the given target node name.
+ *
+ * Returns @c EINA_FALSE if an override with that target already exists or on error.
+ *
+ * @param obj Object being edited.
+ * @param part Part name.
+ * @param state State name (without value).
+ * @param value State value.
+ * @param target Target node name.
+ *
+ * @return @c EINA_TRUE on success, @c EINA_FALSE otherwise.
+ * @since 1.27
+ */
+EAPI Eina_Bool edje_edit_part_vector_override_add(Evas_Object *obj, const char *part, const char *state, double value, const char *target);
+
+/**
+ * @brief Removes the override entry for the given target node name.
+ *
+ * @param obj Object being edited.
+ * @param part Part name.
+ * @param state State name (without value).
+ * @param value State value.
+ * @param target Target node name.
+ *
+ * @return @c EINA_TRUE on success, @c EINA_FALSE otherwise.
+ * @since 1.27
+ */
+EAPI Eina_Bool edje_edit_part_vector_override_del(Evas_Object *obj, const char *part, const char *state, double value, const char *target);
+
+/**
+ * @brief Gets the color-class binding on a node in a named toplevel tree.
+ *
+ * The returned string is a newly added Eina_Stringshare; release with
+ * edje_edit_string_free().
+ *
+ * @param obj Object being edited.
+ * @param tree The named tree to look in.
+ * @param node The node name within that tree.
+ *
+ * @return The color-class name or NULL if none is set.
+ * @since 1.27
+ */
+EAPI const char *edje_edit_vector_node_color_class_get(Evas_Object *obj, const char *tree, const char *node);
+
+/**
+ * @brief Sets the color-class binding on a node in a named toplevel tree.
+ *
+ * Passing NULL for @p class_name clears the binding.
+ *
+ * @param obj Object being edited.
+ * @param tree The named tree.
+ * @param node The node name within that tree.
+ * @param class_name The color class to bind, or NULL to clear.
+ *
+ * @return @c EINA_TRUE on success, @c EINA_FALSE otherwise.
+ * @since 1.27
+ */
+EAPI Eina_Bool edje_edit_vector_node_color_class_set(Evas_Object *obj, const char *tree, const char *node, const char *class_name);
+
+//@}
+
#ifdef __cplusplus
}
#endif
diff --git a/src/lib/edje/edje_cache.c b/src/lib/edje/edje_cache.c
index 9d2cbbd210..8604d19dda 100644
--- a/src/lib/edje/edje_cache.c
+++ b/src/lib/edje/edje_cache.c
@@ -245,7 +245,7 @@ _edje_part_collection_fix(Edje_Part_Collection *edc)
eina_array_flush(&hist);
}
-static Edje_Part_Collection *
+Edje_Part_Collection *
_edje_file_coll_open(Edje_File *edf, const char *coll)
{
Edje_Part_Collection *edc = NULL;
diff --git a/src/lib/edje/edje_edit.c b/src/lib/edje/edje_edit.c
index d54671d657..48f0ccb846 100644
--- a/src/lib/edje/edje_edit.c
+++ b/src/lib/edje/edje_edit.c
@@ -17251,6 +17251,399 @@ edje_edit_print_internal_status(Evas_Object *obj)
*/
}
+/* -------------------------------------------------------------------------
+ * Vector-tree directory API (Phase 8.2)
+ *
+ * These symbols operate on Edje_File.vector_dir — the named Edje_Vg_Tree
+ * directory — as opposed to the image_dir vectors (SVG files stored as
+ * image resources). Naming uses the vg_tree_ infix to avoid collision with
+ * the existing edje_edit_vectors_list_get (image_dir) symbol.
+ * ------------------------------------------------------------------------- */
+
+/* Helper: find a named Edje_Vg_Tree in the file's vector_dir.
+ * Returns the tree array index or -1 if not found. */
+static int
+_edje_edit_vg_tree_id_find(const Edje *ed, const char *name)
+{
+ unsigned int i;
+
+ if (!ed->file || !ed->file->vector_dir || !name) return -1;
+ for (i = 0; i < ed->file->vector_dir->trees_count; ++i)
+ if (ed->file->vector_dir->trees[i].name &&
+ !strcmp(name, ed->file->vector_dir->trees[i].name))
+ return (int)i;
+ return -1;
+}
+
+EAPI Eina_List *
+edje_edit_vg_trees_list_get(Evas_Object *obj)
+{
+ Eina_List *lst = NULL;
+ unsigned int i;
+
+ GET_ED_OR_RETURN(NULL);
+ if (!ed->file || !ed->file->vector_dir) return NULL;
+
+ for (i = 0; i < ed->file->vector_dir->trees_count; ++i)
+ {
+ if (ed->file->vector_dir->trees[i].name)
+ lst = eina_list_append(lst,
+ eina_stringshare_add(ed->file->vector_dir->trees[i].name));
+ }
+ return lst;
+}
+
+EAPI Eina_Bool
+edje_edit_vg_tree_exists(Evas_Object *obj, const char *name)
+{
+ GET_EED_OR_RETURN(EINA_FALSE);
+ if (!name) return EINA_FALSE;
+ return (_edje_edit_vg_tree_id_find(eed->base, name) >= 0)
+ ? EINA_TRUE : EINA_FALSE;
+}
+
+EAPI Eina_Bool
+edje_edit_vg_tree_add(Evas_Object *obj, const char *name)
+{
+ Edje_Vg_Directory *vd;
+ Edje_Vg_Tree *tmp;
+ unsigned int count;
+
+ GET_EED_OR_RETURN(EINA_FALSE);
+ GET_ED_OR_RETURN(EINA_FALSE);
+ if (!name) return EINA_FALSE;
+ if (!ed->file) return EINA_FALSE;
+
+ if (_edje_edit_vg_tree_id_find(ed, name) >= 0) return EINA_FALSE;
+
+ if (!ed->file->vector_dir)
+ {
+ ed->file->vector_dir = _alloc(sizeof(Edje_Vg_Directory));
+ if (!ed->file->vector_dir) return EINA_FALSE;
+ }
+
+ vd = ed->file->vector_dir;
+ count = vd->trees_count + 1;
+ tmp = realloc(vd->trees, sizeof(Edje_Vg_Tree) * count);
+ if (!tmp) return EINA_FALSE;
+
+ vd->trees = tmp;
+ memset(&vd->trees[vd->trees_count], 0, sizeof(Edje_Vg_Tree));
+ vd->trees[vd->trees_count].name = eina_stringshare_add(name);
+ vd->trees_count = count;
+
+ _edje_edit_flag_script_dirty(eed, EINA_TRUE);
+ return EINA_TRUE;
+}
+
+EAPI Eina_Bool
+edje_edit_vg_tree_del(Evas_Object *obj, const char *name)
+{
+ Edje_Vg_Directory *vd;
+ int tree_idx;
+ unsigned int pi, di;
+
+ GET_EED_OR_RETURN(EINA_FALSE);
+ GET_ED_OR_RETURN(EINA_FALSE);
+ if (!name) return EINA_FALSE;
+ if (!ed->file || !ed->file->vector_dir) return EINA_FALSE;
+
+ tree_idx = _edje_edit_vg_tree_id_find(ed, name);
+ if (tree_idx < 0) return EINA_FALSE;
+
+ /* Refuse if any description in any collection references this tree_id.
+ * We must cover BOTH loaded (ce->ref != NULL) and unloaded collections —
+ * unloaded ones are paged in transiently via _edje_file_coll_open so that
+ * a tree referenced only by an unloaded group cannot be silently deleted.
+ * _edje_file_coll_open / _edje_cache_coll_unref use the cache's proper
+ * allocators and avoid the stringshare mismatch that raw eet_data_read
+ * would cause when _edje_collection_free tries to eina_stringshare_del
+ * strings allocated as plain strdup by EET_T_STRING fields. */
+ if (ed->file->collection)
+ {
+ Eina_Iterator *it = eina_hash_iterator_data_new(ed->file->collection);
+ Edje_Part_Collection_Directory_Entry *ce;
+
+ EINA_ITERATOR_FOREACH(it, ce)
+ {
+ Edje_Part_Collection *col;
+ Eina_Bool transient = EINA_FALSE;
+
+ if (ce->ref)
+ {
+ col = ce->ref;
+ }
+ else
+ {
+ /* Page in from the cache / disk. Increments reference count
+ * so we must call _edje_cache_coll_unref when done. */
+ col = _edje_file_coll_open(ed->file, ce->entry);
+ transient = EINA_TRUE;
+ }
+
+ if (!col) continue;
+
+ for (pi = 0; pi < col->parts_count; ++pi)
+ {
+ Edje_Part *ep = col->parts[pi];
+ if (ep->type != EDJE_PART_TYPE_VECTOR) continue;
+ for (di = 0; di < ep->other.desc_count + 1; ++di)
+ {
+ Edje_Part_Description_Vector *vdesc;
+ Edje_Part_Description_Common *base;
+
+ base = (di == 0) ? ep->default_desc
+ : ep->other.desc[di - 1];
+ if (!base) continue;
+ vdesc = (Edje_Part_Description_Vector *)base;
+ if (vdesc->vg.tree_id == tree_idx)
+ {
+ if (transient)
+ _edje_cache_coll_unref(ed->file, col);
+ eina_iterator_free(it);
+ return EINA_FALSE;
+ }
+ }
+ }
+
+ if (transient) _edje_cache_coll_unref(ed->file, col);
+ }
+
+ eina_iterator_free(it);
+ }
+
+ vd = ed->file->vector_dir;
+ /* Free the tree's own fields (name + root node subtree). _edje_vg_tree_free_fields
+ * handles t->name via eina_stringshare_del. In edje_edit, trees are always
+ * built with proper stringshares, so pass EINA_TRUE. */
+ _edje_vg_tree_free_fields(&vd->trees[tree_idx], EINA_TRUE);
+
+ if ((unsigned int)tree_idx < vd->trees_count - 1)
+ {
+ /* Shift the tail down one slot. */
+ memmove(&vd->trees[tree_idx], &vd->trees[tree_idx + 1],
+ sizeof(Edje_Vg_Tree) * (vd->trees_count - (unsigned int)tree_idx - 1));
+
+ /* Fix up tree_id references that pointed past the removed slot. */
+ if (ed->file->collection)
+ {
+ Eina_Iterator *it2 =
+ eina_hash_iterator_data_new(ed->file->collection);
+ Edje_Part_Collection_Directory_Entry *ce2;
+
+ EINA_ITERATOR_FOREACH(it2, ce2)
+ {
+ Edje_Part_Collection *col = ce2->ref;
+ if (!col) continue;
+ for (pi = 0; pi < col->parts_count; ++pi)
+ {
+ Edje_Part *ep = col->parts[pi];
+ if (ep->type != EDJE_PART_TYPE_VECTOR) continue;
+ for (di = 0; di < ep->other.desc_count + 1; ++di)
+ {
+ Edje_Part_Description_Vector *vdesc;
+ Edje_Part_Description_Common *base;
+
+ base = (di == 0) ? ep->default_desc
+ : ep->other.desc[di - 1];
+ if (!base) continue;
+ vdesc = (Edje_Part_Description_Vector *)base;
+ if (vdesc->vg.tree_id > tree_idx)
+ vdesc->vg.tree_id--;
+ }
+ }
+ }
+ eina_iterator_free(it2);
+ }
+ }
+
+ vd->trees_count--;
+ _edje_edit_flag_script_dirty(eed, EINA_TRUE);
+ return EINA_TRUE;
+}
+
+/* -----------------------------------------------------------------------
+ * Per-state tree binding
+ * ----------------------------------------------------------------------- */
+
+EAPI const char *
+edje_edit_part_vector_tree_use_get(Evas_Object *obj, const char *part,
+ const char *state, double value)
+{
+ Edje_Part_Description_Vector *vdesc;
+
+ GET_PD_OR_RETURN(NULL);
+
+ if (rp->part->type != EDJE_PART_TYPE_VECTOR) return NULL;
+ vdesc = (Edje_Part_Description_Vector *)pd;
+ if (vdesc->vg.tree_id < 0) return NULL;
+ if (!ed->file || !ed->file->vector_dir) return NULL;
+ if ((unsigned int)vdesc->vg.tree_id >= ed->file->vector_dir->trees_count)
+ return NULL;
+ return eina_stringshare_add(
+ ed->file->vector_dir->trees[vdesc->vg.tree_id].name);
+}
+
+EAPI Eina_Bool
+edje_edit_part_vector_tree_use_set(Evas_Object *obj, const char *part,
+ const char *state, double value,
+ const char *tree_name)
+{
+ Edje_Part_Description_Vector *vdesc;
+ int tid;
+
+ GET_PD_OR_RETURN(EINA_FALSE);
+
+ if (rp->part->type != EDJE_PART_TYPE_VECTOR) return EINA_FALSE;
+ vdesc = (Edje_Part_Description_Vector *)pd;
+
+ if (!tree_name)
+ {
+ vdesc->vg.tree_id = -1;
+ _edje_edit_flag_script_dirty(eed, EINA_TRUE);
+ return EINA_TRUE;
+ }
+
+ tid = _edje_edit_vg_tree_id_find(ed, tree_name);
+ if (tid < 0) return EINA_FALSE;
+ vdesc->vg.tree_id = tid;
+ _edje_edit_flag_script_dirty(eed, EINA_TRUE);
+ return EINA_TRUE;
+}
+
+/* -----------------------------------------------------------------------
+ * Per-state override list
+ * ----------------------------------------------------------------------- */
+
+EAPI Eina_List *
+edje_edit_part_vector_overrides_list_get(Evas_Object *obj, const char *part,
+ const char *state, double value)
+{
+ Edje_Part_Description_Vector *vdesc;
+ Eina_List *out = NULL, *l;
+ Edje_Vg_Override *ovr;
+
+ GET_PD_OR_RETURN(NULL);
+
+ if (rp->part->type != EDJE_PART_TYPE_VECTOR) return NULL;
+ vdesc = (Edje_Part_Description_Vector *)pd;
+
+ EINA_LIST_FOREACH(vdesc->vg.overrides, l, ovr)
+ {
+ if (ovr->target_name)
+ out = eina_list_append(out, eina_stringshare_add(ovr->target_name));
+ }
+ return out;
+}
+
+EAPI Eina_Bool
+edje_edit_part_vector_override_add(Evas_Object *obj, const char *part,
+ const char *state, double value,
+ const char *target)
+{
+ Edje_Part_Description_Vector *vdesc;
+ Eina_List *l;
+ Edje_Vg_Override *ovr;
+ Edje_Vg_Override *novr;
+
+ GET_PD_OR_RETURN(EINA_FALSE);
+
+ if (!target) return EINA_FALSE;
+ if (rp->part->type != EDJE_PART_TYPE_VECTOR) return EINA_FALSE;
+ vdesc = (Edje_Part_Description_Vector *)pd;
+
+ EINA_LIST_FOREACH(vdesc->vg.overrides, l, ovr)
+ if (ovr->target_name && !strcmp(ovr->target_name, target))
+ return EINA_FALSE;
+
+ novr = _alloc(sizeof(Edje_Vg_Override));
+ if (!novr) return EINA_FALSE;
+ novr->target_name = eina_stringshare_add(target);
+ novr->field_mask = 0;
+ vdesc->vg.overrides = eina_list_append(vdesc->vg.overrides, novr);
+ _edje_edit_flag_script_dirty(eed, EINA_TRUE);
+ return EINA_TRUE;
+}
+
+EAPI Eina_Bool
+edje_edit_part_vector_override_del(Evas_Object *obj, const char *part,
+ const char *state, double value,
+ const char *target)
+{
+ Edje_Part_Description_Vector *vdesc;
+ Eina_List *l;
+ Edje_Vg_Override *ovr;
+
+ GET_PD_OR_RETURN(EINA_FALSE);
+
+ if (!target) return EINA_FALSE;
+ if (rp->part->type != EDJE_PART_TYPE_VECTOR) return EINA_FALSE;
+ vdesc = (Edje_Part_Description_Vector *)pd;
+
+ EINA_LIST_FOREACH(vdesc->vg.overrides, l, ovr)
+ {
+ if (ovr->target_name && !strcmp(ovr->target_name, target))
+ {
+ vdesc->vg.overrides = eina_list_remove(vdesc->vg.overrides, ovr);
+ eina_stringshare_del(ovr->target_name);
+ free(ovr);
+ _edje_edit_flag_script_dirty(eed, EINA_TRUE);
+ return EINA_TRUE;
+ }
+ }
+ return EINA_FALSE;
+}
+
+/* -----------------------------------------------------------------------
+ * Per-node color-class binding (on toplevel-directory trees)
+ * ----------------------------------------------------------------------- */
+
+EAPI const char *
+edje_edit_vector_node_color_class_get(Evas_Object *obj, const char *tree,
+ const char *node)
+{
+ Edje_Vg_Tree *t;
+ Edje_Vg_Node *n;
+ int tid;
+
+ GET_EED_OR_RETURN(NULL);
+ if (!tree || !node) return NULL;
+
+ tid = _edje_edit_vg_tree_id_find(eed->base, tree);
+ if (tid < 0) return NULL;
+ t = &eed->base->file->vector_dir->trees[tid];
+
+ n = _edje_vg_tree_node_by_name(t, node);
+ if (!n) return NULL;
+
+ if (!n->color.color_class) return NULL;
+ return eina_stringshare_add(n->color.color_class);
+}
+
+EAPI Eina_Bool
+edje_edit_vector_node_color_class_set(Evas_Object *obj, const char *tree,
+ const char *node, const char *class_name)
+{
+ Edje_Vg_Tree *t;
+ Edje_Vg_Node *n;
+ int tid;
+
+ GET_EED_OR_RETURN(EINA_FALSE);
+ if (!tree || !node) return EINA_FALSE;
+
+ tid = _edje_edit_vg_tree_id_find(eed->base, tree);
+ if (tid < 0) return EINA_FALSE;
+ t = &eed->base->file->vector_dir->trees[tid];
+
+ n = _edje_vg_tree_node_by_name(t, node);
+ if (!n) return EINA_FALSE;
+
+ eina_stringshare_del(n->color.color_class);
+ n->color.color_class = class_name ? eina_stringshare_add(class_name) : NULL;
+ _edje_edit_flag_script_dirty(eed, EINA_TRUE);
+ return EINA_TRUE;
+}
+
/* Internal EO APIs and hidden overrides */
#define EDJE_EDIT_EXTRA_OPS \
diff --git a/src/lib/edje/edje_load.c b/src/lib/edje/edje_load.c
index cfb16fb7ec..043413b562 100644
--- a/src/lib/edje/edje_load.c
+++ b/src/lib/edje/edje_load.c
@@ -2294,7 +2294,8 @@ _edje_file_free(Edje_File *edf)
* Free each tree's interior heap allocations (root node tree),
* then free the array itself. */
for (i = 0; i < edf->vector_dir->trees_count; i++)
- _edje_vg_tree_free_fields(&edf->vector_dir->trees[i]);
+ _edje_vg_tree_free_fields(&edf->vector_dir->trees[i],
+ edf->free_strings);
free(edf->vector_dir->trees);
/* class_names is a flat array of stringshare strings (Eet decodes
diff --git a/src/lib/edje/edje_private.h b/src/lib/edje/edje_private.h
index f678bbd498..ee7567956c 100644
--- a/src/lib/edje/edje_private.h
+++ b/src/lib/edje/edje_private.h
@@ -2854,6 +2854,7 @@ Edje_File *_edje_file_cache_find(const Eina_File *file);
void _edje_cache_coll_clean(Edje_File *edf);
void _edje_cache_coll_flush(Edje_File *edf);
void _edje_cache_coll_unref(Edje_File *edf, Edje_Part_Collection *edc);
+Edje_Part_Collection *_edje_file_coll_open(Edje_File *edf, const char *coll);
EAPI void edje_cache_emp_alloc(Edje_Part_Collection_Directory_Entry *ce);
EAPI void edje_cache_emp_free(Edje_Part_Collection_Directory_Entry *ce);
EAPI void _edje_cache_file_unref(Edje_File *edf);
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index 26f7a09848..15ed5e9d0c 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -146,26 +146,27 @@ _color_binding_free_fields(Edje_Vg_Color_Binding *cb)
}
EAPI void
-_edje_vg_node_free(Edje_Vg_Node *n)
+_edje_vg_node_free(Edje_Vg_Node *n, Eina_Bool free_strings)
{
if (!n) return;
/* common fields */
- if (n->name) { eina_stringshare_del(n->name); n->name = NULL; }
- _color_binding_free_fields(&n->color);
+ if (free_strings && n->name)
+ { eina_stringshare_del(n->name); n->name = NULL; }
+ if (free_strings && n->color.color_class)
+ { eina_stringshare_del(n->color.color_class); n->color.color_class = NULL; }
switch (n->type)
{
case EDJE_VG_NODE_SHAPE:
- if (n->shape.path)
+ if (free_strings && n->shape.path)
{ eina_stringshare_del(n->shape.path); n->shape.path = NULL; }
- _color_binding_free_fields(&n->shape.fill);
- _color_binding_free_fields(&n->shape.stroke_color);
- if (n->shape.gradient_ref)
- {
- eina_stringshare_del(n->shape.gradient_ref);
- n->shape.gradient_ref = NULL;
- }
+ if (free_strings && n->shape.fill.color_class)
+ { eina_stringshare_del(n->shape.fill.color_class); n->shape.fill.color_class = NULL; }
+ if (free_strings && n->shape.stroke_color.color_class)
+ { eina_stringshare_del(n->shape.stroke_color.color_class); n->shape.stroke_color.color_class = NULL; }
+ if (free_strings && n->shape.gradient_ref)
+ { eina_stringshare_del(n->shape.gradient_ref); n->shape.gradient_ref = NULL; }
free(n->shape.stroke_dash);
n->shape.stroke_dash = NULL;
break;
@@ -174,7 +175,7 @@ _edje_vg_node_free(Edje_Vg_Node *n)
{
Edje_Vg_Node *child;
EINA_LIST_FREE(n->container.children, child)
- _edje_vg_node_free(child);
+ _edje_vg_node_free(child, free_strings);
}
break;
@@ -182,9 +183,16 @@ _edje_vg_node_free(Edje_Vg_Node *n)
case EDJE_VG_NODE_GRADIENT_RADIAL:
if (n->gradient.stops)
{
- unsigned int i;
- for (i = 0; i < n->gradient.stops_count; i++)
- _color_binding_free_fields(&n->gradient.stops[i].color);
+ if (free_strings)
+ {
+ unsigned int i;
+ for (i = 0; i < n->gradient.stops_count; i++)
+ if (n->gradient.stops[i].color.color_class)
+ {
+ eina_stringshare_del(n->gradient.stops[i].color.color_class);
+ n->gradient.stops[i].color.color_class = NULL;
+ }
+ }
free(n->gradient.stops);
n->gradient.stops = NULL;
}
@@ -198,11 +206,12 @@ _edje_vg_node_free(Edje_Vg_Node *n)
}
EAPI void
-_edje_vg_tree_free_fields(Edje_Vg_Tree *t)
+_edje_vg_tree_free_fields(Edje_Vg_Tree *t, Eina_Bool free_strings)
{
if (!t) return;
- if (t->name) { eina_stringshare_del(t->name); t->name = NULL; }
- _edje_vg_node_free(t->root);
+ if (free_strings && t->name)
+ { eina_stringshare_del(t->name); t->name = NULL; }
+ _edje_vg_node_free(t->root, free_strings);
t->root = NULL;
}
@@ -210,7 +219,7 @@ EAPI void
_edje_vg_tree_free(Edje_Vg_Tree *t)
{
if (!t) return;
- _edje_vg_tree_free_fields(t);
+ _edje_vg_tree_free_fields(t, EINA_TRUE);
free(t);
}
diff --git a/src/lib/edje/edje_vg_tree.h b/src/lib/edje/edje_vg_tree.h
index 819fbddc4b..7e532284e6 100644
--- a/src/lib/edje/edje_vg_tree.h
+++ b/src/lib/edje/edje_vg_tree.h
@@ -22,10 +22,17 @@ EAPI Eina_Bool _edje_vg_tree_equal(const Edje_Vg_Tree *a,
/* Free interior heap allocations (root node tree) without freeing t itself.
* Use when t is embedded in a flat array rather than individually heap-alloc'd.
- * Safe to call with NULL. */
-EAPI void _edje_vg_tree_free_fields(Edje_Vg_Tree *t);
+ * Safe to call with NULL.
+ * free_strings: EINA_TRUE — call eina_stringshare_del on all string fields
+ * (use when strings were built with eina_stringshare_add
+ * by the parser or edje_edit helpers).
+ * EINA_FALSE — skip string frees; strings point directly into a
+ * memory-mapped Eet dictionary (edf->free_strings==0). */
+EAPI void _edje_vg_tree_free_fields(Edje_Vg_Tree *t,
+ Eina_Bool free_strings);
-/* Recursive free. Safe to call with NULL. */
+/* Recursive free. Always frees strings (caller owns them as stringshares).
+ * Safe to call with NULL. */
EAPI void _edje_vg_tree_free(Edje_Vg_Tree *t);
/* --- Construction helpers (Phase 2+) ------------------------------------ */
@@ -39,8 +46,10 @@ EAPI Edje_Vg_Tree *_edje_vg_tree_new(void);
* stroke_join=MITER, fill_rule=WINDING, fill RGBA = 255 white. */
EAPI Edje_Vg_Node *_edje_vg_node_new(Edje_Vg_Node_Type type);
-/* Recursive free. Safe to call with NULL. */
-EAPI void _edje_vg_node_free(Edje_Vg_Node *n);
+/* Recursive free. free_strings follows the same convention as
+ * _edje_vg_tree_free_fields — pass EINA_TRUE when strings are stringshares,
+ * EINA_FALSE when they are Eet dictionary pointers. */
+EAPI void _edje_vg_node_free(Edje_Vg_Node *n, Eina_Bool free_strings);
/* Append child to parent (parent must be CONTAINER). No-op on bad args. */
EAPI void _edje_vg_container_append(Edje_Vg_Node *parent,
diff --git a/src/tests/edje/edje_suite.c b/src/tests/edje/edje_suite.c
index 69120c1a12..3928f2a6fb 100644
--- a/src/tests/edje/edje_suite.c
+++ b/src/tests/edje/edje_suite.c
@@ -17,6 +17,7 @@ static const Efl_Test_Case etc[] = {
{ "Text", edje_test_text },
{ "Edje Text", edje_test_text },
{ "Vector States", edje_test_vector_states },
+ { "Edit Vector", edje_test_edit_vector },
{ NULL, NULL }
};
diff --git a/src/tests/edje/edje_suite.h b/src/tests/edje/edje_suite.h
index cbd2ebe7f2..c97fb2956d 100644
--- a/src/tests/edje/edje_suite.h
+++ b/src/tests/edje/edje_suite.h
@@ -14,6 +14,7 @@ void edje_test_signal(TCase *tc);
void edje_test_swallow(TCase *tc);
void edje_test_text(TCase *tc);
void edje_test_vector_states(TCase *tc);
+void edje_test_edit_vector(TCase *tc);
#endif /* _EDJE_SUITE_H */
diff --git a/src/tests/edje/edje_test_edit_vector.c b/src/tests/edje/edje_test_edit_vector.c
new file mode 100644
index 0000000000..18868040ed
--- /dev/null
+++ b/src/tests/edje/edje_test_edit_vector.c
@@ -0,0 +1,406 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <Eina.h>
+#include <Ecore.h>
+#include <Ecore_Evas.h>
+#include "edje_suite.h"
+#define EDJE_EDIT_IS_UNSTABLE_AND_I_KNOW_ABOUT_IT
+#include <Edje_Edit.h>
+
+/* Helper: open the pre-compiled test fixture via edje_edit_object_add. */
+static Evas_Object *
+_edit_obj_new(Ecore_Evas *ee, const char *group)
+{
+ Evas_Object *obj = edje_edit_object_add(ecore_evas_get(ee));
+ if (!obj) return NULL;
+ if (!edje_object_file_set(obj,
+ TESTS_BUILD_DIR "/data/test_vector_states.edj",
+ group))
+ {
+ evas_object_del(obj);
+ return NULL;
+ }
+ return obj;
+}
+
+/* -----------------------------------------------------------------------
+ * edje_edit_vg_trees_list_get
+ * ----------------------------------------------------------------------- */
+START_TEST(test_vg_trees_list_get)
+{
+ Ecore_Evas *ee = ecore_evas_buffer_new(1, 1);
+ fail_if(!ee);
+ Evas_Object *obj = _edit_obj_new(ee, "ui/box_a");
+ fail_if(!obj);
+
+ Eina_List *lst = edje_edit_vg_trees_list_get(obj);
+ /* The fixture defines three named trees: tile_a, tile_b, tile_classed. */
+ ck_assert_int_ge((int)eina_list_count(lst), 3);
+ edje_edit_string_list_free(lst);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+END_TEST
+
+/* -----------------------------------------------------------------------
+ * edje_edit_vg_tree_exists
+ * ----------------------------------------------------------------------- */
+START_TEST(test_vg_tree_exists)
+{
+ Ecore_Evas *ee = ecore_evas_buffer_new(1, 1);
+ fail_if(!ee);
+ Evas_Object *obj = _edit_obj_new(ee, "ui/box_a");
+ fail_if(!obj);
+
+ ck_assert(edje_edit_vg_tree_exists(obj, "tile_a") == EINA_TRUE);
+ ck_assert(edje_edit_vg_tree_exists(obj, "tile_b") == EINA_TRUE);
+ ck_assert(edje_edit_vg_tree_exists(obj, "no_such_tree") == EINA_FALSE);
+ ck_assert(edje_edit_vg_tree_exists(obj, NULL) == EINA_FALSE);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+END_TEST
+
+/* -----------------------------------------------------------------------
+ * edje_edit_vg_tree_add — duplicate name returns EINA_FALSE
+ * ----------------------------------------------------------------------- */
+START_TEST(test_vg_tree_add)
+{
+ Ecore_Evas *ee = ecore_evas_buffer_new(1, 1);
+ fail_if(!ee);
+ Evas_Object *obj = _edit_obj_new(ee, "ui/box_a");
+ fail_if(!obj);
+
+ Eina_List *lst_before = edje_edit_vg_trees_list_get(obj);
+ unsigned int before = eina_list_count(lst_before);
+ edje_edit_string_list_free(lst_before);
+
+ /* Adding a new unique name must succeed. */
+ ck_assert(edje_edit_vg_tree_add(obj, "brand_new_tree") == EINA_TRUE);
+ ck_assert(edje_edit_vg_tree_exists(obj, "brand_new_tree") == EINA_TRUE);
+
+ Eina_List *lst_after = edje_edit_vg_trees_list_get(obj);
+ ck_assert_int_eq((int)eina_list_count(lst_after), (int)(before + 1));
+ edje_edit_string_list_free(lst_after);
+
+ /* Adding the same name again must fail. */
+ ck_assert(edje_edit_vg_tree_add(obj, "brand_new_tree") == EINA_FALSE);
+ ck_assert(edje_edit_vg_tree_add(obj, "tile_a") == EINA_FALSE);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+END_TEST
+
+/* -----------------------------------------------------------------------
+ * edje_edit_vg_tree_del — refuses to remove in-use trees
+ * ----------------------------------------------------------------------- */
+START_TEST(test_vg_tree_del)
+{
+ Ecore_Evas *ee = ecore_evas_buffer_new(1, 1);
+ fail_if(!ee);
+ Evas_Object *obj = _edit_obj_new(ee, "ui/box_a");
+ fail_if(!obj);
+
+ /* tile_a is referenced by ui/box_a (loaded group) — del must refuse. */
+ ck_assert(edje_edit_vg_tree_del(obj, "tile_a") == EINA_FALSE);
+ ck_assert(edje_edit_vg_tree_exists(obj, "tile_a") == EINA_TRUE);
+
+ /* Non-existent name must also fail gracefully. */
+ ck_assert(edje_edit_vg_tree_del(obj, "does_not_exist") == EINA_FALSE);
+
+ /* Add a fresh tree and then delete it — must succeed. */
+ ck_assert(edje_edit_vg_tree_add(obj, "tmp_tree") == EINA_TRUE);
+ ck_assert(edje_edit_vg_tree_del(obj, "tmp_tree") == EINA_TRUE);
+ ck_assert(edje_edit_vg_tree_exists(obj, "tmp_tree") == EINA_FALSE);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+END_TEST
+
+/* -----------------------------------------------------------------------
+ * edje_edit_vg_tree_del — index-fixup path coverage
+ *
+ * Adds two fresh trees (A at slot N, B at slot N+1), binds a description to B,
+ * deletes A, and asserts the binding now resolves correctly to B.
+ * This exercises the memmove + tree_id-- fixup loop that was not covered by
+ * test_vg_tree_del (which only deleted the last entry).
+ * ----------------------------------------------------------------------- */
+START_TEST(test_vg_tree_del_fixup)
+{
+ Ecore_Evas *ee = ecore_evas_buffer_new(1, 1);
+ fail_if(!ee);
+ Evas_Object *obj = _edit_obj_new(ee, "ui/box_a");
+ fail_if(!obj);
+
+ /* Add two fresh unreferenced trees. */
+ ck_assert(edje_edit_vg_tree_add(obj, "fixup_a") == EINA_TRUE);
+ ck_assert(edje_edit_vg_tree_add(obj, "fixup_b") == EINA_TRUE);
+
+ /* Bind the vg part default description to fixup_b. */
+ ck_assert(edje_edit_part_vector_tree_use_set(obj, "vg", "default", 0.0,
+ "fixup_b") == EINA_TRUE);
+
+ /* Confirm binding resolves to fixup_b before deletion. */
+ const char *bound = edje_edit_part_vector_tree_use_get(obj, "vg", "default", 0.0);
+ fail_if(!bound);
+ ck_assert_str_eq(bound, "fixup_b");
+ edje_edit_string_free(bound);
+
+ /* Delete fixup_a (comes before fixup_b in the directory array).
+ * The fixup loop must decrement the tree_id stored in the vg description. */
+ ck_assert(edje_edit_vg_tree_del(obj, "fixup_a") == EINA_TRUE);
+ ck_assert(edje_edit_vg_tree_exists(obj, "fixup_a") == EINA_FALSE);
+ ck_assert(edje_edit_vg_tree_exists(obj, "fixup_b") == EINA_TRUE);
+
+ /* The binding must still resolve to fixup_b despite the index shift. */
+ bound = edje_edit_part_vector_tree_use_get(obj, "vg", "default", 0.0);
+ fail_if(!bound);
+ ck_assert_str_eq(bound, "fixup_b");
+ edje_edit_string_free(bound);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+END_TEST
+
+/* -----------------------------------------------------------------------
+ * edje_edit_part_vector_tree_use_get/set
+ * ----------------------------------------------------------------------- */
+START_TEST(test_part_vector_tree_use_get_set)
+{
+ Ecore_Evas *ee = ecore_evas_buffer_new(1, 1);
+ fail_if(!ee);
+ Evas_Object *obj = _edit_obj_new(ee, "ui/box_a");
+ fail_if(!obj);
+
+ /* The default description uses tile_a. */
+ const char *name = edje_edit_part_vector_tree_use_get(obj, "vg", "default", 0.0);
+ fail_if(!name);
+ ck_assert_str_eq(name, "tile_a");
+ edje_edit_string_free(name);
+
+ /* Switch to tile_b. */
+ ck_assert(edje_edit_part_vector_tree_use_set(obj, "vg", "default", 0.0, "tile_b")
+ == EINA_TRUE);
+ name = edje_edit_part_vector_tree_use_get(obj, "vg", "default", 0.0);
+ fail_if(!name);
+ ck_assert_str_eq(name, "tile_b");
+ edje_edit_string_free(name);
+
+ /* Clear binding via NULL. */
+ ck_assert(edje_edit_part_vector_tree_use_set(obj, "vg", "default", 0.0, NULL)
+ == EINA_TRUE);
+ name = edje_edit_part_vector_tree_use_get(obj, "vg", "default", 0.0);
+ ck_assert(name == NULL);
+
+ /* Non-existent tree name must fail. */
+ ck_assert(edje_edit_part_vector_tree_use_set(obj, "vg", "default", 0.0, "ghost_tree")
+ == EINA_FALSE);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+END_TEST
+
+/* -----------------------------------------------------------------------
+ * edje_edit_part_vector_overrides_list_get
+ * ----------------------------------------------------------------------- */
+START_TEST(test_part_vector_overrides_list_get)
+{
+ Ecore_Evas *ee = ecore_evas_buffer_new(1, 1);
+ fail_if(!ee);
+ /* box_a_hover's hover state has one override on "body". */
+ Evas_Object *obj = _edit_obj_new(ee, "ui/box_a_hover");
+ fail_if(!obj);
+
+ Eina_List *lst = edje_edit_part_vector_overrides_list_get(obj, "vg", "hover", 0.0);
+ ck_assert_int_eq((int)eina_list_count(lst), 1);
+ ck_assert_str_eq(eina_list_data_get(lst), "body");
+ edje_edit_string_list_free(lst);
+
+ /* Default state has no overrides. */
+ lst = edje_edit_part_vector_overrides_list_get(obj, "vg", "default", 0.0);
+ ck_assert_int_eq((int)eina_list_count(lst), 0);
+ edje_edit_string_list_free(lst);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+END_TEST
+
+/* -----------------------------------------------------------------------
+ * edje_edit_part_vector_override_add / _del
+ * ----------------------------------------------------------------------- */
+START_TEST(test_part_vector_override_add_del)
+{
+ Ecore_Evas *ee = ecore_evas_buffer_new(1, 1);
+ fail_if(!ee);
+ Evas_Object *obj = _edit_obj_new(ee, "ui/box_a");
+ fail_if(!obj);
+
+ Eina_List *lst = edje_edit_part_vector_overrides_list_get(obj, "vg", "default", 0.0);
+ int before = (int)eina_list_count(lst);
+ edje_edit_string_list_free(lst);
+
+ /* Add a new override. */
+ ck_assert(edje_edit_part_vector_override_add(obj, "vg", "default", 0.0, "new_target")
+ == EINA_TRUE);
+ lst = edje_edit_part_vector_overrides_list_get(obj, "vg", "default", 0.0);
+ ck_assert_int_eq((int)eina_list_count(lst), before + 1);
+ edje_edit_string_list_free(lst);
+
+ /* Duplicate add must fail. */
+ ck_assert(edje_edit_part_vector_override_add(obj, "vg", "default", 0.0, "new_target")
+ == EINA_FALSE);
+
+ /* Delete it. */
+ ck_assert(edje_edit_part_vector_override_del(obj, "vg", "default", 0.0, "new_target")
+ == EINA_TRUE);
+ lst = edje_edit_part_vector_overrides_list_get(obj, "vg", "default", 0.0);
+ ck_assert_int_eq((int)eina_list_count(lst), before);
+ edje_edit_string_list_free(lst);
+
+ /* Delete non-existent must fail. */
+ ck_assert(edje_edit_part_vector_override_del(obj, "vg", "default", 0.0, "ghost")
+ == EINA_FALSE);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+END_TEST
+
+/* -----------------------------------------------------------------------
+ * edje_edit_vector_node_color_class_get/set
+ * ----------------------------------------------------------------------- */
+START_TEST(test_vector_node_color_class_get_set)
+{
+ Ecore_Evas *ee = ecore_evas_buffer_new(1, 1);
+ fail_if(!ee);
+ Evas_Object *obj = _edit_obj_new(ee, "ui/box_a");
+ fail_if(!obj);
+
+ /* tile_a/body has no color_class by default. */
+ const char *cls = edje_edit_vector_node_color_class_get(obj, "tile_a", "body");
+ ck_assert(cls == NULL);
+
+ /* Set a class. */
+ ck_assert(edje_edit_vector_node_color_class_set(obj, "tile_a", "body", "my_class")
+ == EINA_TRUE);
+ cls = edje_edit_vector_node_color_class_get(obj, "tile_a", "body");
+ fail_if(!cls);
+ ck_assert_str_eq(cls, "my_class");
+ edje_edit_string_free(cls);
+
+ /* Clear via NULL. */
+ ck_assert(edje_edit_vector_node_color_class_set(obj, "tile_a", "body", NULL)
+ == EINA_TRUE);
+ cls = edje_edit_vector_node_color_class_get(obj, "tile_a", "body");
+ ck_assert(cls == NULL);
+
+ /* tile_classed/body already has fill.color_class set via EDC but not the
+ * node-level color_class — test the already-classed tree as a sanity check. */
+ ck_assert(edje_edit_vector_node_color_class_set(obj, "tile_classed", "body", "extra_class")
+ == EINA_TRUE);
+ cls = edje_edit_vector_node_color_class_get(obj, "tile_classed", "body");
+ fail_if(!cls);
+ ck_assert_str_eq(cls, "extra_class");
+ edje_edit_string_free(cls);
+
+ /* Unknown tree must fail. */
+ ck_assert(edje_edit_vector_node_color_class_set(obj, "no_tree", "body", "x")
+ == EINA_FALSE);
+ /* Unknown node must fail. */
+ ck_assert(edje_edit_vector_node_color_class_set(obj, "tile_a", "no_node", "x")
+ == EINA_FALSE);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+END_TEST
+
+/* -----------------------------------------------------------------------
+ * Round-trip: add tree, set binding, save, reload, verify
+ * ----------------------------------------------------------------------- */
+START_TEST(test_vg_edit_round_trip)
+{
+ Ecore_Evas *ee = ecore_evas_buffer_new(1, 1);
+ fail_if(!ee);
+
+ /* Work on a temporary copy to avoid corrupting the test fixture. */
+ char tmp[] = "/tmp/edje_test_edit_vector_XXXXXX.edj";
+ int fd = mkstemps(tmp, 4);
+ fail_if(fd < 0);
+ close(fd);
+
+ {
+ /* Copy fixture -> tmp using ecore_file_cp. */
+ const char *src = "" "/data/test_vector_states.edj";
+ FILE *in = fopen(src, "rb");
+ fail_if(!in);
+ FILE *out = fopen(tmp, "wb");
+ fail_if(!out);
+ char buf[4096];
+ size_t n;
+ while ((n = fread(buf, 1, sizeof(buf), in)) > 0)
+ fwrite(buf, 1, n, out);
+ fclose(in);
+ fclose(out);
+ }
+
+ Evas_Object *obj = edje_edit_object_add(ecore_evas_get(ee));
+ fail_if(!obj);
+ fail_unless(edje_object_file_set(obj, tmp, "ui/box_a"));
+
+ /* Add a new tree entry. */
+ ck_assert(edje_edit_vg_tree_add(obj, "rt_tree") == EINA_TRUE);
+
+ /* Bind it to the default state. */
+ ck_assert(edje_edit_part_vector_tree_use_set(obj, "vg", "default", 0.0, "tile_b")
+ == EINA_TRUE);
+
+ /* Save. */
+ ck_assert(edje_edit_save(obj) == EINA_TRUE);
+ evas_object_del(obj);
+
+ /* Reload and verify. */
+ obj = edje_edit_object_add(ecore_evas_get(ee));
+ fail_if(!obj);
+ fail_unless(edje_object_file_set(obj, tmp, "ui/box_a"));
+
+ ck_assert(edje_edit_vg_tree_exists(obj, "rt_tree") == EINA_TRUE);
+
+ const char *bound = edje_edit_part_vector_tree_use_get(obj, "vg", "default", 0.0);
+ fail_if(!bound);
+ ck_assert_str_eq(bound, "tile_b");
+ edje_edit_string_free(bound);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+ unlink(tmp);
+}
+END_TEST
+
+void
+edje_test_edit_vector(TCase *tc)
+{
+ tcase_add_test(tc, test_vg_trees_list_get);
+ tcase_add_test(tc, test_vg_tree_exists);
+ tcase_add_test(tc, test_vg_tree_add);
+ tcase_add_test(tc, test_vg_tree_del);
+ tcase_add_test(tc, test_vg_tree_del_fixup);
+ tcase_add_test(tc, test_part_vector_tree_use_get_set);
+ tcase_add_test(tc, test_part_vector_overrides_list_get);
+ tcase_add_test(tc, test_part_vector_override_add_del);
+ tcase_add_test(tc, test_vector_node_color_class_get_set);
+ tcase_add_test(tc, test_vg_edit_round_trip);
+}
diff --git a/src/tests/edje/meson.build b/src/tests/edje/meson.build
index 709a387316..4f69a8ec86 100644
--- a/src/tests/edje/meson.build
+++ b/src/tests/edje/meson.build
@@ -11,6 +11,7 @@ edje_suite_src = [
'edje_test_swallow.c',
'edje_test_text.c',
'edje_test_vector_states.c',
+ 'edje_test_edit_vector.c',
'efl_test_layout.c',
'efl_test_layout2.c',
'efl_test_layout3.c',
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.