From b0e785c6c269f5223fe2beaa1794f850e270d9cd Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Fri, 10 Jul 2015 11:26:34 +0100 Subject: [PATCH] actor: Add bind_model_with_properties() When binding models to actors to map items to children we don't often need the full control of a function; in many cases we just need to specify the type of the child we want to construct and the properties on both the item and the child that we want to bind. We should provide a simple convenience function that does all this for us. --- clutter/clutter-actor.c | 135 +++++++++++++++++++++++++++++ clutter/clutter-actor.h | 6 ++ doc/reference/clutter-sections.txt | 1 + examples/actor-model.c | 33 ++----- 4 files changed, 151 insertions(+), 24 deletions(-) diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c index d6f7c7d9e..1a0431293 100644 --- a/clutter/clutter-actor.c +++ b/clutter/clutter-actor.c @@ -20895,3 +20895,138 @@ clutter_actor_bind_model (ClutterActor *self, g_list_model_get_n_items (priv->child_model), self); } + +typedef struct { + GType child_type; + GArray *props; +} BindClosure; + +typedef struct { + const char *model_property; + const char *child_property; + GBindingFlags flags; +} BindProperty; + +static void +bind_closure_free (gpointer data_) +{ + BindClosure *data = data_; + + if (data == NULL) + return; + + g_array_unref (data->props); + g_slice_free (BindClosure, data); +} + +static ClutterActor * +bind_child_with_properties (gpointer item, + gpointer data_) +{ + BindClosure *data = data_; + ClutterActor *res; + guint i; + + res = g_object_new (data->child_type, NULL); + + for (i = 0; i < data->props->len; i++) + { + const BindProperty *prop = &g_array_index (data->props, BindProperty, i); + + g_object_bind_property (item, prop->model_property, + res, prop->child_property, + prop->flags); + } + + return res; +} + +/** + * clutter_actor_bind_model_with_properties: + * @self: a #ClutterActor + * @model: a #GListModel + * @child_type: the type of #ClutterActor to use when creating + * children mapping to items inside the @model + * @first_model_property: the first property of @model to bind + * @...: tuples of property names on the @model, on the child, and the + * #GBindingFlags used to bind them, terminated by %NULL + * + * Binds a #GListModel to a #ClutterActor. + * + * Unlike clutter_actor_bind_model(), this function automatically creates + * a child #ClutterActor of type @child_type, and binds properties on the + * items inside the @model to the corresponding properties on the child, + * for instance: + * + * |[ + * clutter_actor_bind_model_with_properties (actor, model, + * MY_TYPE_CHILD_VIEW, + * "label", "text", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE, + * "icon", "image", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE, + * "selected", "selected", G_BINDING_BIDIRECTIONAL, + * "active", "active", G_BINDING_BIDIRECTIONAL, + * NULL); + * ]| + * + * is the equivalent of calling clutter_actor_bind_model() with a + * #ClutterActorCreateChildFunc of: + * + * |[ + * ClutterActor *res = g_object_new (MY_TYPE_CHILD_VIEW, NULL); + * + * g_object_bind_property (item, "label", res, "text", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + * g_object_bind_property (item, "icon", res, "image", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + * g_object_bind_property (item, "selected", res, "selected", G_BINDING_BIDIRECTIONAL); + * g_object_bind_property (item, "active", res, "active", G_BINDING_BIDIRECTIONAL); + * + * return res; + * ]| + * + * If the #ClutterActor was already bound to a #GListModel, the previous + * binding is destroyed. + * + * When a #ClutterActor is bound to a model, adding and removing children + * directly is undefined behaviour. + * + * See also: clutter_actor_bind_model() + * + * Since: 1.24 + */ +void +clutter_actor_bind_model_with_properties (ClutterActor *self, + GListModel *model, + GType child_type, + const char *first_model_property, + ...) +{ + va_list args; + BindClosure *clos; + const char *model_property; + + g_return_if_fail (CLUTTER_IS_ACTOR (self)); + g_return_if_fail (G_IS_LIST_MODEL (model)); + g_return_if_fail (g_type_is_a (child_type, CLUTTER_TYPE_ACTOR)); + + clos = g_slice_new0 (BindClosure); + clos->child_type = child_type; + clos->props = g_array_new (FALSE, FALSE, sizeof (BindProperty)); + + va_start (args, first_model_property); + model_property = first_model_property; + while (model_property != NULL) + { + const char *child_property = va_arg (args, char *); + GBindingFlags binding_flags = va_arg (args, guint); + BindProperty bind; + + bind.model_property = g_intern_string (model_property); + bind.child_property = g_intern_string (child_property); + bind.flags = binding_flags; + + g_array_append_val (clos->props, bind); + + model_property = va_arg (args, char *); + } + + clutter_actor_bind_model (self, model, bind_child_with_properties, clos, bind_closure_free); +} diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h index a821a61c5..6949d9743 100644 --- a/clutter/clutter-actor.h +++ b/clutter/clutter-actor.h @@ -877,6 +877,12 @@ void clutter_actor_bind_model ClutterActorCreateChildFunc create_child_func, gpointer user_data, GDestroyNotify notify); +CLUTTER_AVAILABLE_IN_1_24 +void clutter_actor_bind_model_with_properties (ClutterActor *self, + GListModel *model, + GType child_type, + const char *first_model_property, + ...); G_END_DECLS diff --git a/doc/reference/clutter-sections.txt b/doc/reference/clutter-sections.txt index 0e727fba1..3880923c8 100644 --- a/doc/reference/clutter-sections.txt +++ b/doc/reference/clutter-sections.txt @@ -467,6 +467,7 @@ clutter_actor_iter_remove clutter_actor_iter_destroy ClutterActorCreateChildFunc clutter_actor_bind_model +clutter_actor_bind_model_with_properties clutter_actor_save_easing_state diff --git a/examples/actor-model.c b/examples/actor-model.c index 18547f1b3..4a6ec778d 100644 --- a/examples/actor-model.c +++ b/examples/actor-model.c @@ -429,27 +429,6 @@ on_model_item_selection (GObject *model_item, g_free (label); } -static ClutterActor * -create_menu_item (gpointer item, - gpointer data G_GNUC_UNUSED) -{ - ClutterActor *res = g_object_new (EXAMPLE_TYPE_MENU_ITEM_VIEW, NULL); - - /* The label goes from the model to the view */ - g_object_bind_property (item, "label", - res, "text", - G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); - - /* The selected state goes in either direction */ - g_object_bind_property (item, "selected", - res, "selected", - G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); - - g_signal_connect (item, "notify::selected", G_CALLBACK (on_model_item_selection), NULL); - - return res; -} - static ClutterActor * create_menu_actor (void) { @@ -469,6 +448,10 @@ create_menu_actor (void) g_list_store_append (model, item); + g_signal_connect (item, "notify::selected", + G_CALLBACK (on_model_item_selection), + NULL); + g_object_unref (item); g_free (label); } @@ -477,9 +460,11 @@ create_menu_actor (void) * create ClutterActor views of each item in the model, and add them * to the menu actor */ - clutter_actor_bind_model (menu, G_LIST_MODEL (model), - create_menu_item, - NULL, NULL); + clutter_actor_bind_model_with_properties (menu, G_LIST_MODEL (model), + EXAMPLE_TYPE_MENU_ITEM_VIEW, + "label", "text", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE, + "selected", "selected", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE, + NULL); /* We don't need a pointer to the model any more, so we transfer ownership * to the menu actor; this means that the model will go away when the menu