/* dzl-properties-group.c * * Copyright (C) 2017 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define G_LOG_DOMAIN "dzl-properties-group" #include "config.h" #include "dzl-properties-group.h" /** * SECTION:dzl-properties-group * @title: DzlPropertiesGroup * @short_description: A #GActionGroup of properties on an object * * This class is a #GActionGroup which provides stateful access to * properties in a #GObject. This can be useful when you want to * expose properties from a GObject as a #GAction, espectially with * use in GtkApplications. * * Call dzl_properties_group_add_property() to setup the mappings * for action-name to property-name for the actions you'd like to * add. * * Not all property types can be supported. What is current supported * are properties of type: * * %G_TYPE_INT * %G_TYPE_UINT * %G_TYPE_BOOLEAN * %G_TYPE_STRING * %G_TYPE_DOUBLE * * Since: 3.26 * * Since 3.28 enums are supported by using their enum value nick as * a string. */ struct _DzlPropertiesGroup { GObject parent_instance; /* * All subsequent set_object() calls must be this type. */ GType prerequisite; /* * Weak ref to the object we are monitoring for property changes. * We hold both a GWeakRef and a g_object_weak_ref() on the object * so that we can get notified of destruction *AND* know when we * can safely weak_unref() without invalid pointer user. */ GWeakRef object_ref; /* * Since the list of mappings are fairly small, we just choose to * use an array of all mappings rather than two-hashtables to map * from action-name -> property-name and vice versa. Element type * is of struct Mapping. * * The strings in the mapping are intern'd to allow for direct * pointer comparison with GParamSpec information. */ GArray *mappings; }; typedef struct { const gchar *action_name; const GVariantType *param_type; const GVariantType *state_type; const gchar *property_name; GType property_type; DzlPropertiesFlags flags : 8; guint can_read : 1; guint can_write : 1; } Mapping; enum { PROP_0, PROP_OBJECT, PROP_OBJECT_TYPE, N_PROPS }; static GVariant * get_action_state (GObject *object, const Mapping *mapping) { g_auto(GValue) value = G_VALUE_INIT; GVariant *ret = NULL; g_assert (G_IS_OBJECT (object)); g_assert (mapping != NULL); if (!mapping->can_read) return NULL; g_value_init (&value, mapping->property_type); g_object_get_property (object, mapping->property_name, &value); switch (mapping->property_type) { case G_TYPE_INT: ret = g_variant_new_int32 (g_value_get_int (&value)); break; case G_TYPE_UINT: ret = g_variant_new_uint32 (g_value_get_uint (&value)); break; case G_TYPE_DOUBLE: ret = g_variant_new_double (g_value_get_double (&value)); break; case G_TYPE_STRING: if (!g_value_get_string (&value)) ret = g_variant_new_string (""); else ret = g_variant_new_string (g_value_get_string (&value)); break; case G_TYPE_BOOLEAN: ret = g_variant_new_boolean (g_value_get_boolean (&value)); break; default: if (g_type_is_a (mapping->property_type, G_TYPE_ENUM)) { GEnumClass *eclass = g_type_class_ref (mapping->property_type); GEnumValue *eval = g_enum_get_value (eclass, g_value_get_enum (&value)); if (eval != NULL) ret = g_variant_new_string (eval->value_nick); g_clear_pointer (&eclass, g_type_class_unref); break; } g_assert_not_reached (); } return g_variant_take_ref (ret); } static gboolean dzl_properties_group_query_action (GActionGroup *group, const gchar *action_name, gboolean *enabled, const GVariantType **param_type, const GVariantType **state_type, GVariant **state_hint, GVariant **state) { DzlPropertiesGroup *self = (DzlPropertiesGroup *)group; g_assert (DZL_IS_PROPERTIES_GROUP (self)); g_assert (action_name != NULL); for (guint i = 0; i < self->mappings->len; i++) { const Mapping *mapping = &g_array_index (self->mappings, Mapping, i); if (g_strcmp0 (mapping->action_name, action_name) == 0) { g_autoptr(GObject) object = g_weak_ref_get (&self->object_ref); if (enabled) *enabled = (object != NULL); if (param_type) *param_type = mapping->param_type; if (state_type) *state_type = mapping->state_type; if (state_hint) *state_hint = NULL; if (state) { if (object) *state = get_action_state (object, mapping); else *state = NULL; } return TRUE; } } return FALSE; } static gchar ** dzl_properties_group_list_actions (GActionGroup *group) { DzlPropertiesGroup *self = (DzlPropertiesGroup *)group; GPtrArray *ar; g_assert (DZL_IS_PROPERTIES_GROUP (self)); ar = g_ptr_array_new (); for (guint i = 0; i < self->mappings->len; i++) { const Mapping *mapping = &g_array_index (self->mappings, Mapping, i); g_ptr_array_add (ar, g_strdup (mapping->action_name)); } g_ptr_array_add (ar, NULL); return (gchar **)g_ptr_array_free (ar, FALSE); } static gboolean dzl_properties_group_has_action (GActionGroup *group, const gchar *name) { DzlPropertiesGroup *self = (DzlPropertiesGroup *)group; g_assert (DZL_IS_PROPERTIES_GROUP (self)); g_assert (name != NULL); for (guint i = 0; i < self->mappings->len; i++) { const Mapping *mapping = &g_array_index (self->mappings, Mapping, i); if (g_strcmp0 (name, mapping->action_name) == 0) return TRUE; } return FALSE; } static gboolean dzl_properties_group_get_action_enabled (GActionGroup *group, const gchar *name) { DzlPropertiesGroup *self = (DzlPropertiesGroup *)group; g_autoptr(GObject) object = NULL; g_assert (DZL_IS_PROPERTIES_GROUP (self)); g_assert (name != NULL); object = g_weak_ref_get (&self->object_ref); return (object != NULL); } static const GVariantType * dzl_properties_group_get_action_parameter_type (GActionGroup *group, const gchar *name) { DzlPropertiesGroup *self = (DzlPropertiesGroup *)group; g_assert (DZL_IS_PROPERTIES_GROUP (self)); g_assert (name != NULL); for (guint i = 0; i < self->mappings->len; i++) { const Mapping *mapping = &g_array_index (self->mappings, Mapping, i); if (g_strcmp0 (name, mapping->action_name) == 0) { /* Normal parameter type for boolean actions is NULL. But if * we are treating them statefully, where the new state is the * activation state, then handle that here. */ if (mapping->property_type == G_TYPE_BOOLEAN && (mapping->flags & DZL_PROPERTIES_FLAGS_STATEFUL_BOOLEANS) != 0) return G_VARIANT_TYPE_BOOLEAN; return mapping->param_type; } } return NULL; } static const GVariantType * dzl_properties_group_get_action_state_type (GActionGroup *group, const gchar *name) { DzlPropertiesGroup *self = (DzlPropertiesGroup *)group; g_assert (DZL_IS_PROPERTIES_GROUP (self)); g_assert (name != NULL); for (guint i = 0; i < self->mappings->len; i++) { const Mapping *mapping = &g_array_index (self->mappings, Mapping, i); if (g_strcmp0 (name, mapping->action_name) == 0) return mapping->state_type; } return NULL; } static GVariant * dzl_properties_group_get_action_state_hint (GActionGroup *group, const gchar *name) { g_assert (DZL_IS_PROPERTIES_GROUP (group)); g_assert (name != NULL); return NULL; } static void dzl_properties_group_change_action_state (GActionGroup *group, const gchar *name, GVariant *variant) { DzlPropertiesGroup *self = (DzlPropertiesGroup *)group; g_autoptr(GObject) object = NULL; const GVariantType *expected; g_assert (DZL_IS_PROPERTIES_GROUP (self)); g_assert (name != NULL); expected = dzl_properties_group_get_action_state_type (group, name); if (variant == NULL || !g_variant_is_of_type (variant, expected)) { g_warning ("Invalid state for action \"%s\". Expected %s.", name, (const gchar *)expected); return; } object = g_weak_ref_get (&self->object_ref); if (object == NULL) { g_warning ("Attempt to change state of %s after action was disabled", name); return; } for (guint i = 0; i < self->mappings->len; i++) { const Mapping *mapping = &g_array_index (self->mappings, Mapping, i); if (g_strcmp0 (name, mapping->action_name) == 0) { g_auto(GValue) value = G_VALUE_INIT; if (!mapping->can_write) { g_warning ("property is not writable, ignoring request to change state"); break; } switch (mapping->property_type) { case G_TYPE_INT: g_value_init (&value, G_TYPE_INT); g_value_set_int (&value, g_variant_get_int32 (variant)); break; case G_TYPE_UINT: g_value_init (&value, G_TYPE_UINT); g_value_set_uint (&value, g_variant_get_uint32 (variant)); break; case G_TYPE_BOOLEAN: g_value_init (&value, G_TYPE_BOOLEAN); g_value_set_boolean (&value, g_variant_get_boolean (variant)); break; case G_TYPE_STRING: g_value_init (&value, G_TYPE_STRING); /* No need to dup the string, its lifetime is longer */ g_value_set_static_string (&value, g_variant_get_string (variant, NULL)); break; case G_TYPE_DOUBLE: g_value_init (&value, G_TYPE_DOUBLE); g_value_set_double (&value, g_variant_get_double (variant)); break; default: if (g_type_is_a (mapping->property_type, G_TYPE_ENUM)) { const gchar *str = g_variant_get_string (variant, NULL); GEnumClass *eclass = g_type_class_ref (mapping->property_type); if (eclass != NULL) { GEnumValue *eval = g_enum_get_value_by_nick (eclass, str); if (eval != NULL) { g_value_init (&value, mapping->property_type); g_value_set_enum (&value, eval->value); g_clear_pointer (&eclass, g_type_class_unref); break; } } g_clear_pointer (&eclass, g_type_class_unref); g_warning ("Failed to transform '%s' to %s", str, g_type_name (mapping->property_type)); return; } g_assert_not_reached (); } g_object_set_property (object, mapping->property_name, &value); break; } } } static void dzl_properties_group_activate_action (GActionGroup *group, const gchar *name, GVariant *variant) { DzlPropertiesGroup *self = (DzlPropertiesGroup *)group; g_autoptr(GObject) object = NULL; g_assert (DZL_IS_PROPERTIES_GROUP (self)); g_assert (name != NULL); object = g_weak_ref_get (&self->object_ref); if (object == NULL) { g_warning ("Attempt to activate %s after action was disabled", name); return; } for (guint i = 0; i < self->mappings->len; i++) { const Mapping *mapping = &g_array_index (self->mappings, Mapping, i); if (g_strcmp0 (name, mapping->action_name) == 0) { if (mapping->property_type == G_TYPE_BOOLEAN && (mapping->flags & DZL_PROPERTIES_FLAGS_STATEFUL_BOOLEANS) == 0) { gboolean value = FALSE; g_object_get (object, mapping->property_name, &value, NULL); value = !value; g_object_set (object, mapping->property_name, value, NULL); } else { dzl_properties_group_change_action_state (group, name, variant); } break; } } } static void action_group_iface_init (GActionGroupInterface *iface) { iface->has_action = dzl_properties_group_has_action; iface->list_actions = dzl_properties_group_list_actions; iface->get_action_enabled = dzl_properties_group_get_action_enabled; iface->get_action_parameter_type = dzl_properties_group_get_action_parameter_type; iface->get_action_state_type = dzl_properties_group_get_action_state_type; iface->get_action_state_hint = dzl_properties_group_get_action_state_hint; iface->change_action_state = dzl_properties_group_change_action_state; iface->activate_action = dzl_properties_group_activate_action; iface->query_action = dzl_properties_group_query_action; } G_DEFINE_TYPE_WITH_CODE (DzlPropertiesGroup, dzl_properties_group, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, action_group_iface_init)) static GParamSpec *properties [N_PROPS]; static void dzl_properties_group_notify (DzlPropertiesGroup *self, GParamSpec *pspec, GObject *object) { g_assert (DZL_IS_PROPERTIES_GROUP (self)); g_assert (pspec != NULL); g_assert (G_IS_OBJECT (object)); /* mappings is generally quite small, so iterating the array * is going to have similar performance to a hashtable lookup * plus pointer chaseing. */ for (guint i = 0; i < self->mappings->len; i++) { const Mapping *mapping = &g_array_index (self->mappings, Mapping, i); if (mapping->property_name == pspec->name) { g_autoptr(GVariant) state = get_action_state (object, mapping); g_action_group_action_state_changed (G_ACTION_GROUP (self), mapping->action_name, state); break; } } } static const GVariantType * get_param_type_for_type (GType type, DzlPropertiesFlags flags) { switch (type) { case G_TYPE_INT: return G_VARIANT_TYPE_INT32; case G_TYPE_UINT: return G_VARIANT_TYPE_UINT32; case G_TYPE_STRING: return G_VARIANT_TYPE_STRING; case G_TYPE_DOUBLE: return G_VARIANT_TYPE_DOUBLE; case G_TYPE_BOOLEAN: if (flags & DZL_PROPERTIES_FLAGS_STATEFUL_BOOLEANS) return G_VARIANT_TYPE_BOOLEAN; return NULL; default: if (g_type_is_a (type, G_TYPE_ENUM)) return G_VARIANT_TYPE_STRING; g_warning ("%s is not a supported type", g_type_name (type)); return NULL; } } static const GVariantType * get_state_type_for_type (GType type) { switch (type) { case G_TYPE_INT: return G_VARIANT_TYPE_INT32; case G_TYPE_UINT: return G_VARIANT_TYPE_UINT32; case G_TYPE_BOOLEAN: return G_VARIANT_TYPE_BOOLEAN; case G_TYPE_STRING: return G_VARIANT_TYPE_STRING; case G_TYPE_DOUBLE: return G_VARIANT_TYPE_DOUBLE; default: if (g_type_is_a (type, G_TYPE_ENUM)) return G_VARIANT_TYPE_STRING; g_warning ("%s is not a supported type", g_type_name (type)); return NULL; } } static void dzl_properties_group_notify_all_disabled (DzlPropertiesGroup *self) { g_assert (DZL_IS_PROPERTIES_GROUP (self)); for (guint i = 0; i < self->mappings->len; i++) { const Mapping *mapping = &g_array_index (self->mappings, Mapping, i); g_action_group_action_enabled_changed (G_ACTION_GROUP (self), mapping->action_name, FALSE); } } static void dzl_properties_group_weak_notify (gpointer data, GObject *where_object_was) { DzlPropertiesGroup *self = data; g_assert (DZL_IS_PROPERTIES_GROUP (self)); g_weak_ref_set (&self->object_ref, NULL); dzl_properties_group_notify_all_disabled (self); } static void dzl_properties_group_set_object (DzlPropertiesGroup *self, GObject *object) { g_autoptr(GObject) old_object = NULL; g_assert (DZL_IS_PROPERTIES_GROUP (self)); g_assert (!object || G_IS_OBJECT (object)); old_object = g_weak_ref_get (&self->object_ref); /* Nothing to do if we aren't changing anything */ if (object == old_object) return; if (self->prerequisite == G_TYPE_INVALID && object != NULL) self->prerequisite = G_OBJECT_TYPE (object); /* Disconnect previous life-cycle tracking */ if (old_object != NULL) { g_signal_handlers_disconnect_by_func (old_object, G_CALLBACK (dzl_properties_group_notify), self); g_object_weak_unref (old_object, dzl_properties_group_weak_notify, self); g_weak_ref_set (&self->object_ref, NULL); } /* Mark all actions as disabled if we lost our object */ if (object == NULL) { dzl_properties_group_notify_all_disabled (self); return; } g_signal_connect_object (object, "notify", G_CALLBACK (dzl_properties_group_notify), self, G_CONNECT_SWAPPED); /* WeakRef so we can detect 3-rd degree disposal */ g_weak_ref_set (&self->object_ref, object); /* Weak notify so we can get notified of the case */ g_object_weak_ref (G_OBJECT (object), dzl_properties_group_weak_notify, self); /* Emit state changes for all properties */ for (guint i = 0; i < self->mappings->len; i++) { const Mapping *mapping = &g_array_index (self->mappings, Mapping, i); g_autoptr(GVariant) state = get_action_state (object, mapping); g_action_group_action_state_changed (G_ACTION_GROUP (self), mapping->action_name, state); } } static void dzl_properties_group_finalize (GObject *object) { DzlPropertiesGroup *self = (DzlPropertiesGroup *)object; g_autoptr(GObject) weak_obj = NULL; weak_obj = g_weak_ref_get (&self->object_ref); if (weak_obj != NULL) { /* * No need to disconnect signal handler as we are in finalize and * g_signal_connect_object() tracks this for us. */ g_object_weak_unref (weak_obj, dzl_properties_group_weak_notify, self); } g_weak_ref_clear (&self->object_ref); g_clear_pointer (&self->mappings, g_array_unref); G_OBJECT_CLASS (dzl_properties_group_parent_class)->finalize (object); } static void dzl_properties_group_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { DzlPropertiesGroup *self = DZL_PROPERTIES_GROUP (object); switch (prop_id) { case PROP_OBJECT: g_value_take_object (value, g_weak_ref_get (&self->object_ref)); break; case PROP_OBJECT_TYPE: g_value_set_gtype (value, self->prerequisite); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void dzl_properties_group_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { DzlPropertiesGroup *self = DZL_PROPERTIES_GROUP (object); switch (prop_id) { case PROP_OBJECT: dzl_properties_group_set_object (self, g_value_get_object (value)); break; case PROP_OBJECT_TYPE: if (g_value_get_gtype (value) != G_TYPE_INVALID && g_value_get_gtype (value) != G_TYPE_OBJECT) self->prerequisite = g_value_get_gtype (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void dzl_properties_group_class_init (DzlPropertiesGroupClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = dzl_properties_group_finalize; object_class->get_property = dzl_properties_group_get_property; object_class->set_property = dzl_properties_group_set_property; properties [PROP_OBJECT] = g_param_spec_object ("object", "Object", "The source object for the properties", G_TYPE_OBJECT, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); properties [PROP_OBJECT_TYPE] = g_param_spec_gtype ("object-type", "Object Type", "A type the object must conform to.", G_TYPE_OBJECT, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); } static void dzl_properties_group_init (DzlPropertiesGroup *self) { g_weak_ref_init (&self->object_ref, NULL); self->mappings = g_array_new (FALSE, FALSE, sizeof (Mapping)); } /** * dzl_properties_group_new: * @object: The object containing the properties * * This creates a new #DzlPropertiesGroup to create stateful actions * around properties in @object. * * Call dzl_properties_group_add_property() to add a property to * action name mapping for this group. Until you've called this, * no actions are mapped. * * Note that #DzlPropertiesGroup only holds a weak reference to * @object and therefore you must keep @object alive elsewhere. * * Returns: (transfer full): A #DzlPropertiesGroup * * Since: 3.26 */ DzlPropertiesGroup * dzl_properties_group_new (GObject *object) { g_return_val_if_fail (G_IS_OBJECT (object), NULL); return g_object_new (DZL_TYPE_PROPERTIES_GROUP, "object", object, "object-type", G_OBJECT_TYPE (object), NULL); } /** * dzl_properties_group_add_property_full: * @self: a #DzlPropertiesGroup * @name: the name of the action * @property_name: the name of the property * @flags: optional flags for the action * * Adds a new stateful action named @name which maps to the underlying * property @property_name of #DzlPropertiesGroup:object. * * Seting @flags allows you to tweak some settings about the action. * * Since: 3.26 */ void dzl_properties_group_add_property_full (DzlPropertiesGroup *self, const gchar *name, const gchar *property_name, DzlPropertiesFlags flags) { GObjectClass *object_class = NULL; GParamSpec *pspec; Mapping mapping = { 0 }; g_return_if_fail (DZL_IS_PROPERTIES_GROUP (self)); g_return_if_fail (name != NULL); g_return_if_fail (property_name != NULL); if (self->prerequisite == G_TYPE_INVALID) { g_warning ("Cannot add properties before object has been set."); return; } object_class = g_type_class_ref (self->prerequisite); if (object_class == NULL || !G_IS_OBJECT_CLASS (object_class)) { g_warning ("Implausable result for prerequisite, not a GObjectClass"); goto failure; } pspec = g_object_class_find_property (object_class, property_name); if (pspec == NULL) { g_warning ("No such property \"%s\" on type %s", property_name, G_OBJECT_CLASS_NAME (object_class)); goto failure; } mapping.action_name = g_intern_string (name); mapping.param_type = get_param_type_for_type (pspec->value_type, flags); mapping.state_type = get_state_type_for_type (pspec->value_type); mapping.property_name = pspec->name; mapping.property_type = pspec->value_type; mapping.flags = flags; mapping.can_read = !!(pspec->flags & G_PARAM_READABLE); mapping.can_write = !!(pspec->flags & G_PARAM_WRITABLE); /* we already warned, ignore this */ if (mapping.state_type == NULL) goto failure; g_array_append_val (self->mappings, mapping); g_action_group_action_added (G_ACTION_GROUP (self), mapping.action_name); failure: g_clear_pointer (&object_class, g_type_class_unref); } /** * dzl_properties_group_add_property: * @self: a #DzlPropertiesGroup * @name: the name of the action * @property_name: the name of the property * * Adds a new stateful action named @name which maps to the underlying * property @property_name of #DzlPropertiesGroup:object. * * Since: 3.26 */ void dzl_properties_group_add_property (DzlPropertiesGroup *self, const gchar *name, const gchar *property_name) { dzl_properties_group_add_property_full (self, name, property_name, 0); } /** * dzl_properties_group_remove: * @self: a #DzlPropertiesGroup * @name: the name of the action * * Removes an action from @self that was previously added with * dzl_properties_group_add_property(). @name should match the * name parameter to that function. * * Since: 3.26 */ void dzl_properties_group_remove (DzlPropertiesGroup *self, const gchar *name) { g_return_if_fail (DZL_IS_PROPERTIES_GROUP (self)); g_return_if_fail (name != NULL); name = g_intern_string (name); for (guint i = 0; i < self->mappings->len; i++) { const Mapping *mapping = &g_array_index (self->mappings, Mapping, i); if (mapping->action_name == name) { g_array_remove_index_fast (self->mappings, i); g_action_group_action_removed (G_ACTION_GROUP (self), name); break; } } } /** * dzl_properties_group_add_all_properties: * @self: A #DzlPropertiesGroup * * This function will try to add all properties found on the target * instance to the group. Only properties that are supported by the * #DzlPropertiesGroup will be added. * * The action name of all added properties will be identical to their * property name. * * Since: 3.26 */ void dzl_properties_group_add_all_properties (DzlPropertiesGroup *self) { g_autofree GParamSpec **pspec = NULL; GObjectClass *object_class = NULL; guint n_pspec = 0; g_return_if_fail (DZL_IS_PROPERTIES_GROUP (self)); if (self->prerequisite == G_TYPE_INVALID) { g_warning ("Cannot add properties, no object has been set"); return; } object_class = g_type_class_ref (self->prerequisite); if (object_class == NULL || !G_IS_OBJECT_CLASS (object_class)) { g_warning ("Implausable result, not a GObjectClass"); goto failure; } pspec = g_object_class_list_properties (object_class, &n_pspec); for (guint i = 0; i < n_pspec; i++) { switch (pspec[i]->value_type) { case G_TYPE_BOOLEAN: case G_TYPE_DOUBLE: case G_TYPE_INT: case G_TYPE_STRING: case G_TYPE_UINT: dzl_properties_group_add_property (self, pspec[i]->name, pspec[i]->name); break; default: if (g_type_is_a (pspec[i]->value_type, G_TYPE_ENUM)) dzl_properties_group_add_property (self, pspec[i]->name, pspec[i]->name); break; } } failure: g_clear_pointer (&object_class, g_type_class_unref); } /** * dzl_properties_group_new_for_type: * @object_type: A #GObjectClass based type * * This creates a new #DzlPropertiesGroup for which the initial object is * %NULL. * * Set @object_type to a type of a class which is a #GObject-based type. * * Returns: (transfer none): A #DzlPropertiesGroup. */ DzlPropertiesGroup * dzl_properties_group_new_for_type (GType object_type) { g_return_val_if_fail (g_type_is_a (object_type, G_TYPE_OBJECT), NULL); return g_object_new (DZL_TYPE_PROPERTIES_GROUP, "object-type", object_type, NULL); }