diff options
author | Christian Hergert <chergert@redhat.com> | 2021-08-27 13:01:00 -0700 |
---|---|---|
committer | Christian Hergert <chergert@redhat.com> | 2022-02-01 17:09:14 -0800 |
commit | dd43471f606466e239757e33bfd1bdeee152b36e (patch) | |
tree | ae59472066598437eddd3f7925017c5476b582bf | |
parent | 0d9de09192a1984865154d5c6f39578a956b27c4 (diff) | |
download | glib-dd43471f606466e239757e33bfd1bdeee152b36e.tar.gz |
gobject: add GSignalGroup
Much like GBindingGroup, the GSignalGroup object allows you to connect many
signal connections for an object and connect/disconnect/block/unblock them
as a group.
This is useful when using many connections on an object to ensure that they
are properly removed when changing state or disposing a third-party
object.
This has been used for years in various GNOME projects and makes sense to
have upstream instead of multiple copies.
-rw-r--r-- | docs/reference/gobject/gobject-docs.xml | 1 | ||||
-rw-r--r-- | docs/reference/gobject/gobject-sections.txt | 22 | ||||
-rw-r--r-- | glib/glib-object.h | 1 | ||||
-rw-r--r-- | gobject/gsignalgroup.c | 912 | ||||
-rw-r--r-- | gobject/gsignalgroup.h | 93 | ||||
-rw-r--r-- | gobject/meson.build | 2 | ||||
-rw-r--r-- | gobject/tests/meson.build | 1 | ||||
-rw-r--r-- | gobject/tests/signalgroup.c | 650 |
8 files changed, 1682 insertions, 0 deletions
diff --git a/docs/reference/gobject/gobject-docs.xml b/docs/reference/gobject/gobject-docs.xml index 1718d9e23..aa5a9c722 100644 --- a/docs/reference/gobject/gobject-docs.xml +++ b/docs/reference/gobject/gobject-docs.xml @@ -81,6 +81,7 @@ <xi:include href="xml/gparamspec.xml" /> <xi:include href="xml/value_collection.xml" /> <xi:include href="xml/signals.xml" /> + <xi:include href="xml/gsignalgroup.xml" /> <xi:include href="xml/gclosure.xml" /> <xi:include href="xml/value_arrays.xml" /> <xi:include href="xml/gbinding.xml" /> diff --git a/docs/reference/gobject/gobject-sections.txt b/docs/reference/gobject/gobject-sections.txt index 2450a367d..cbab92406 100644 --- a/docs/reference/gobject/gobject-sections.txt +++ b/docs/reference/gobject/gobject-sections.txt @@ -1024,3 +1024,25 @@ G_IS_BINDING_GROUP <SUBSECTION Private> g_binding_group_get_type </SECTION> + +<SECTION> +<FILE>gsignalgroup</FILE> +GSignalGroup +g_signal_group_block +g_signal_group_connect +g_signal_group_connect_after +g_signal_group_connect_data +g_signal_group_connect_object +g_signal_group_connect_swapped +g_signal_group_dup_target +g_signal_group_get_type +g_signal_group_new +g_signal_group_set_target +g_signal_group_unblock +<SUBSECTION Standard> +G_IS_SIGNAL_GROUP +G_SIGNAL_GROUP +G_TYPE_SIGNAL_GROUP +<SUBSECTION Private> +g_signal_group_get_type +</SECTION> diff --git a/glib/glib-object.h b/glib/glib-object.h index d259a3d39..915a29901 100644 --- a/glib/glib-object.h +++ b/glib/glib-object.h @@ -28,6 +28,7 @@ #include <gobject/gparam.h> #include <gobject/gparamspecs.h> #include <gobject/gsignal.h> +#include <gobject/gsignalgroup.h> #include <gobject/gsourceclosure.h> #include <gobject/gtype.h> #include <gobject/gtypemodule.h> diff --git a/gobject/gsignalgroup.c b/gobject/gsignalgroup.c new file mode 100644 index 000000000..8feba543c --- /dev/null +++ b/gobject/gsignalgroup.c @@ -0,0 +1,912 @@ +/* GObject - GLib Type, Object, Parameter and Signal Library + * + * Copyright (C) 2015-2022 Christian Hergert <christian@hergert.me> + * Copyright (C) 2015 Garrett Regier <garrettregier@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" +#include "glib.h" +#include "glibintl.h" + +#include "gparamspecs.h" +#include "gsignalgroup.h" +#include "gvaluetypes.h" + +/** + * SECTION:gsignalgroup + * @Title: GSignalGroup + * @Short_description: Manage a collection of signals on a GObject + * + * #GSignalGroup manages to simplify the process of connecting + * many signals to a #GObject as a group. As such there is no API + * to disconnect a signal from the group. + * + * In particular, this allows you to: + * + * - Change the target instance, which automatically causes disconnection + * of the signals from the old instance and connecting to the new instance. + * - Block and unblock signals as a group + * - Ensuring that blocked state transfers across target instances. + * + * One place you might want to use such a structure is with #GtkTextView and + * #GtkTextBuffer. Often times, you'll need to connect to many signals on + * #GtkTextBuffer from a #GtkTextView subclass. This allows you to create a + * signal group during instance construction, simply bind the + * #GtkTextView:buffer property to #GSignalGroup:target and connect + * all the signals you need. When the #GtkTextView:buffer property changes + * all of the signals will be transitioned correctly. + * + * Since: 2.72 + */ + +struct _GSignalGroup +{ + GObject parent_instance; + + GWeakRef target_ref; + GRecMutex mutex; + GPtrArray *handlers; + GType target_type; + gssize block_count; + + guint has_bound_at_least_once : 1; +}; + +typedef struct _GSignalGroupClass +{ + GObjectClass parent_class; + + void (*bind) (GSignalGroup *self, + GObject *target); +} GSignalGroupClass; + +typedef struct +{ + GSignalGroup *group; + gulong handler_id; + GClosure *closure; + guint signal_id; + GQuark signal_detail; + guint connect_after : 1; +} SignalHandler; + +G_DEFINE_TYPE (GSignalGroup, g_signal_group, G_TYPE_OBJECT) + +typedef enum +{ + PROP_TARGET = 1, + PROP_TARGET_TYPE, + LAST_PROP +} GSignalGroupProperty; + +enum +{ + BIND, + UNBIND, + LAST_SIGNAL +}; + +static GParamSpec *properties[LAST_PROP]; +static guint signals[LAST_SIGNAL]; + +static void +g_signal_group_set_target_type (GSignalGroup *self, + GType target_type) +{ + g_assert (G_IS_SIGNAL_GROUP (self)); + g_assert (g_type_is_a (target_type, G_TYPE_OBJECT)); + + self->target_type = target_type; + + /* The class must be created at least once for the signals + * to be registered, otherwise g_signal_parse_name() will fail + */ + if (G_TYPE_IS_INTERFACE (target_type)) + { + if (g_type_default_interface_peek (target_type) == NULL) + g_type_default_interface_unref (g_type_default_interface_ref (target_type)); + } + else + { + if (g_type_class_peek (target_type) == NULL) + g_type_class_unref (g_type_class_ref (target_type)); + } +} + +static void +g_signal_group_gc_handlers (GSignalGroup *self) +{ + guint i; + + g_assert (G_IS_SIGNAL_GROUP (self)); + + /* + * Remove any handlers for which the closures have become invalid. We do + * this cleanup lazily to avoid situations where we could have disposal + * active on both the signal group and the peer object. + */ + + for (i = self->handlers->len; i > 0; i--) + { + const SignalHandler *handler = g_ptr_array_index (self->handlers, i - 1); + + g_assert (handler != NULL); + g_assert (handler->closure != NULL); + + if (handler->closure->is_invalid) + g_ptr_array_remove_index (self->handlers, i - 1); + } +} + +static void +g_signal_group__target_weak_notify (gpointer data, + GObject *where_object_was) +{ + GSignalGroup *self = data; + guint i; + + g_assert (G_IS_SIGNAL_GROUP (self)); + g_assert (where_object_was != NULL); + + g_rec_mutex_lock (&self->mutex); + + g_weak_ref_set (&self->target_ref, NULL); + + for (i = 0; i < self->handlers->len; i++) + { + SignalHandler *handler = g_ptr_array_index (self->handlers, i); + + handler->handler_id = 0; + } + + g_signal_emit (self, signals[UNBIND], 0); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TARGET]); + + g_rec_mutex_unlock (&self->mutex); +} + +static void +g_signal_group_bind_handler (GSignalGroup *self, + SignalHandler *handler, + GObject *target) +{ + gssize i; + + g_assert (self != NULL); + g_assert (G_IS_OBJECT (target)); + g_assert (handler != NULL); + g_assert (handler->signal_id != 0); + g_assert (handler->closure != NULL); + g_assert (handler->closure->is_invalid == 0); + g_assert (handler->handler_id == 0); + + handler->handler_id = g_signal_connect_closure_by_id (target, + handler->signal_id, + handler->signal_detail, + handler->closure, + handler->connect_after); + + g_assert (handler->handler_id != 0); + + for (i = 0; i < self->block_count; i++) + g_signal_handler_block (target, handler->handler_id); +} + +static void +g_signal_group_bind (GSignalGroup *self, + GObject *target) +{ + GObject *hold; + guint i; + + g_assert (G_IS_SIGNAL_GROUP (self)); + g_assert (!target || G_IS_OBJECT (target)); + + if (target == NULL) + return; + + self->has_bound_at_least_once = TRUE; + + hold = g_object_ref (target); + + g_weak_ref_set (&self->target_ref, hold); + g_object_weak_ref (hold, g_signal_group__target_weak_notify, self); + + g_signal_group_gc_handlers (self); + + for (i = 0; i < self->handlers->len; i++) + { + SignalHandler *handler = g_ptr_array_index (self->handlers, i); + + g_signal_group_bind_handler (self, handler, hold); + } + + g_signal_emit (self, signals [BIND], 0, hold); + + g_object_unref (hold); +} + +static void +g_signal_group_unbind (GSignalGroup *self) +{ + GObject *target; + guint i; + + g_return_if_fail (G_IS_SIGNAL_GROUP (self)); + + target = g_weak_ref_get (&self->target_ref); + + /* + * Target may be NULL by this point, as we got notified of its destruction. + * However, if we're early enough, we may get a full reference back and can + * cleanly disconnect our connections. + */ + + if (target != NULL) + { + g_weak_ref_set (&self->target_ref, NULL); + + /* + * Let go of our weak reference now that we have a full reference + * for the life of this function. + */ + g_object_weak_unref (target, + g_signal_group__target_weak_notify, + self); + } + + g_signal_group_gc_handlers (self); + + for (i = 0; i < self->handlers->len; i++) + { + SignalHandler *handler; + gulong handler_id; + + handler = g_ptr_array_index (self->handlers, i); + + g_assert (handler != NULL); + g_assert (handler->signal_id != 0); + g_assert (handler->closure != NULL); + + handler_id = handler->handler_id; + handler->handler_id = 0; + + /* + * If @target is NULL, we lost a race to cleanup the weak + * instance and the signal connections have already been + * finalized and therefore nothing to do. + */ + + if (target != NULL && handler_id != 0) + g_signal_handler_disconnect (target, handler_id); + } + + g_signal_emit (self, signals [UNBIND], 0); + + g_clear_object (&target); +} + +static gboolean +g_signal_group_check_target_type (GSignalGroup *self, + gpointer target) +{ + if ((target != NULL) && + !g_type_is_a (G_OBJECT_TYPE (target), self->target_type)) + { + g_critical ("Failed to set GSignalGroup of target type %s " + "using target %p of type %s", + g_type_name (self->target_type), + target, G_OBJECT_TYPE_NAME (target)); + return FALSE; + } + + return TRUE; +} + +/** + * g_signal_group_block: + * @self: the #GSignalGroup + * + * Blocks all signal handlers managed by @self so they will not + * be called during any signal emissions. Must be unblocked exactly + * the same number of times it has been blocked to become active again. + * + * This blocked state will be kept across changes of the target instance. + * + * Since: 2.72 + */ +void +g_signal_group_block (GSignalGroup *self) +{ + GObject *target; + guint i; + + g_return_if_fail (G_IS_SIGNAL_GROUP (self)); + g_return_if_fail (self->block_count >= 0); + + g_rec_mutex_lock (&self->mutex); + + self->block_count++; + + target = g_weak_ref_get (&self->target_ref); + + if (target == NULL) + goto unlock; + + for (i = 0; i < self->handlers->len; i++) + { + const SignalHandler *handler = g_ptr_array_index (self->handlers, i); + + g_assert (handler != NULL); + g_assert (handler->signal_id != 0); + g_assert (handler->closure != NULL); + g_assert (handler->handler_id != 0); + + g_signal_handler_block (target, handler->handler_id); + } + + g_object_unref (target); + +unlock: + g_rec_mutex_unlock (&self->mutex); +} + +/** + * g_signal_group_unblock: + * @self: the #GSignalGroup + * + * Unblocks all signal handlers managed by @self so they will be + * called again during any signal emissions unless it is blocked + * again. Must be unblocked exactly the same number of times it + * has been blocked to become active again. + * + * Since: 2.72 + */ +void +g_signal_group_unblock (GSignalGroup *self) +{ + GObject *target; + guint i; + + g_return_if_fail (G_IS_SIGNAL_GROUP (self)); + g_return_if_fail (self->block_count > 0); + + g_rec_mutex_lock (&self->mutex); + + self->block_count--; + + target = g_weak_ref_get (&self->target_ref); + if (target == NULL) + goto unlock; + + for (i = 0; i < self->handlers->len; i++) + { + const SignalHandler *handler = g_ptr_array_index (self->handlers, i); + + g_assert (handler != NULL); + g_assert (handler->signal_id != 0); + g_assert (handler->closure != NULL); + g_assert (handler->handler_id != 0); + + g_signal_handler_unblock (target, handler->handler_id); + } + + g_object_unref (target); + +unlock: + g_rec_mutex_unlock (&self->mutex); +} + +/** + * g_signal_group_dup_target: + * @self: the #GSignalGroup + * + * Gets the target instance used when connecting signals. + * + * Returns: (nullable) (transfer full) (type GObject): The target instance + * + * Since: 2.72 + */ +gpointer +g_signal_group_dup_target (GSignalGroup *self) +{ + GObject *target; + + g_return_val_if_fail (G_IS_SIGNAL_GROUP (self), NULL); + + g_rec_mutex_lock (&self->mutex); + target = g_weak_ref_get (&self->target_ref); + g_rec_mutex_unlock (&self->mutex); + + return target; +} + +/** + * g_signal_group_set_target: + * @self: the #GSignalGroup. + * @target: (nullable) (type GObject) (transfer none): The target instance used + * when connecting signals. + * + * Sets the target instance used when connecting signals. Any signal + * that has been registered with g_signal_group_connect_object() or + * similar functions will be connected to this object. + * + * If the target instance was previously set, signals will be + * disconnected from that object prior to connecting to @target. + * + * Since: 2.72 + */ +void +g_signal_group_set_target (GSignalGroup *self, + gpointer target) +{ + GObject *object; + + g_return_if_fail (G_IS_SIGNAL_GROUP (self)); + + g_rec_mutex_lock (&self->mutex); + + object = g_weak_ref_get (&self->target_ref); + + if (object == (GObject *)target) + goto cleanup; + + if (!g_signal_group_check_target_type (self, target)) + goto cleanup; + + /* Only emit unbind if we've ever called bind */ + if (self->has_bound_at_least_once) + g_signal_group_unbind (self); + + g_signal_group_bind (self, target); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TARGET]); + +cleanup: + g_clear_object (&object); + g_rec_mutex_unlock (&self->mutex); +} + +static void +signal_handler_free (gpointer data) +{ + SignalHandler *handler = data; + + if (handler->closure != NULL) + g_closure_invalidate (handler->closure); + + handler->handler_id = 0; + handler->signal_id = 0; + handler->signal_detail = 0; + g_clear_pointer (&handler->closure, g_closure_unref); + g_slice_free (SignalHandler, handler); +} + +static void +g_signal_group_constructed (GObject *object) +{ + GSignalGroup *self = (GSignalGroup *)object; + GObject *target; + + g_rec_mutex_lock (&self->mutex); + + target = g_weak_ref_get (&self->target_ref); + if (!g_signal_group_check_target_type (self, target)) + g_signal_group_set_target (self, NULL); + + G_OBJECT_CLASS (g_signal_group_parent_class)->constructed (object); + + g_clear_object (&target); + + g_rec_mutex_unlock (&self->mutex); +} + +static void +g_signal_group_dispose (GObject *object) +{ + GSignalGroup *self = (GSignalGroup *)object; + + g_rec_mutex_lock (&self->mutex); + + g_signal_group_gc_handlers (self); + + if (self->has_bound_at_least_once) + g_signal_group_unbind (self); + + g_clear_pointer (&self->handlers, g_ptr_array_unref); + + g_rec_mutex_unlock (&self->mutex); + + G_OBJECT_CLASS (g_signal_group_parent_class)->dispose (object); +} + +static void +g_signal_group_finalize (GObject *object) +{ + GSignalGroup *self = (GSignalGroup *)object; + + g_weak_ref_clear (&self->target_ref); + g_rec_mutex_clear (&self->mutex); + + G_OBJECT_CLASS (g_signal_group_parent_class)->finalize (object); +} + +static void +g_signal_group_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GSignalGroup *self = G_SIGNAL_GROUP (object); + + switch ((GSignalGroupProperty) prop_id) + { + case PROP_TARGET: + g_value_take_object (value, g_signal_group_dup_target (self)); + break; + + case PROP_TARGET_TYPE: + g_value_set_gtype (value, self->target_type); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +g_signal_group_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GSignalGroup *self = G_SIGNAL_GROUP (object); + + switch ((GSignalGroupProperty) prop_id) + { + case PROP_TARGET: + g_signal_group_set_target (self, g_value_get_object (value)); + break; + + case PROP_TARGET_TYPE: + g_signal_group_set_target_type (self, g_value_get_gtype (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +g_signal_group_class_init (GSignalGroupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = g_signal_group_constructed; + object_class->dispose = g_signal_group_dispose; + object_class->finalize = g_signal_group_finalize; + object_class->get_property = g_signal_group_get_property; + object_class->set_property = g_signal_group_set_property; + + /** + * GSignalGroup:target + * + * The target instance used when connecting signals. + * + * Since: 2.72 + */ + properties[PROP_TARGET] = + g_param_spec_object ("target", + "Target", + "The target instance used when connecting signals.", + G_TYPE_OBJECT, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + /** + * GSignalGroup:target-type + * + * The #GType of the target property. + * + * Since: 2.72 + */ + properties[PROP_TARGET_TYPE] = + g_param_spec_gtype ("target-type", + "Target Type", + "The GType of the target property.", + G_TYPE_OBJECT, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, LAST_PROP, properties); + + /** + * GSignalGroup::bind: + * @self: the #GSignalGroup + * @instance: a #GObject containing the new value for #GSignalGroup:target + * + * This signal is emitted when #GSignalGroup:target is set to a new value + * other than %NULL. It is similar to #GObject::notify on `target` except it + * will not emit when #GSignalGroup:target is %NULL and also allows for + * receiving the #GObject without a data-race. + * + * Since: 2.72 + */ + signals[BIND] = + g_signal_new ("bind", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); + + /** + * GSignalGroup::unbind: + * @self: a #GSignalGroup + * + * This signal is emitted when the target instance of @self is set to a + * new #GObject. + * + * This signal will only be emitted if the previous target of @self is + * non-%NULL. + * + * Since: 2.72 + */ + signals[UNBIND] = + g_signal_new ("unbind", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 0); +} + +static void +g_signal_group_init (GSignalGroup *self) +{ + g_rec_mutex_init (&self->mutex); + self->handlers = g_ptr_array_new_with_free_func (signal_handler_free); + self->target_type = G_TYPE_OBJECT; +} + +/** + * g_signal_group_new: + * @target_type: the #GType of the target instance. + * + * Creates a new #GSignalGroup for target instances of @target_type. + * + * Returns: (transfer full): a new #GSignalGroup + * + * Since: 2.72 + */ +GSignalGroup * +g_signal_group_new (GType target_type) +{ + g_return_val_if_fail (g_type_is_a (target_type, G_TYPE_OBJECT), NULL); + + return g_object_new (G_TYPE_SIGNAL_GROUP, + "target-type", target_type, + NULL); +} + +static void +g_signal_group_connect_full (GSignalGroup *self, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data, + GClosureNotify notify, + GConnectFlags flags, + gboolean is_object) +{ + GObject *target; + SignalHandler *handler; + GClosure *closure; + guint signal_id; + GQuark signal_detail; + + g_return_if_fail (G_IS_SIGNAL_GROUP (self)); + g_return_if_fail (detailed_signal != NULL); + g_return_if_fail (g_signal_parse_name (detailed_signal, self->target_type, + &signal_id, &signal_detail, TRUE) != 0); + g_return_if_fail (c_handler != NULL); + g_return_if_fail (!is_object || G_IS_OBJECT (data)); + + g_rec_mutex_lock (&self->mutex); + + if (self->has_bound_at_least_once) + { + g_critical ("Cannot add signals after setting target"); + g_rec_mutex_unlock (&self->mutex); + return; + } + + if ((flags & G_CONNECT_SWAPPED) != 0) + closure = g_cclosure_new_swap (c_handler, data, notify); + else + closure = g_cclosure_new (c_handler, data, notify); + + handler = g_slice_new0 (SignalHandler); + handler->group = self; + handler->signal_id = signal_id; + handler->signal_detail = signal_detail; + handler->closure = g_closure_ref (closure); + handler->connect_after = ((flags & G_CONNECT_AFTER) != 0); + + g_closure_sink (closure); + + if (is_object) + { + /* Set closure->is_invalid when data is disposed. We only track this to avoid + * reconnecting in the future. However, we do a round of cleanup when ever we + * connect a new object or the target changes to GC the old handlers. + */ + g_object_watch_closure (data, closure); + } + + g_ptr_array_add (self->handlers, handler); + + target = g_weak_ref_get (&self->target_ref); + + if (target != NULL) + { + g_signal_group_bind_handler (self, handler, target); + g_object_unref (target); + } + + /* Lazily remove any old handlers on connect */ + g_signal_group_gc_handlers (self); + + g_rec_mutex_unlock (&self->mutex); +} + +/** + * g_signal_group_connect_object: (skip) + * @self: a #GSignalGroup + * @detailed_signal: a string of the form `signal-name` with optional `::signal-detail` + * @c_handler: (scope notified): the #GCallback to connect + * @object: (not nullable) (transfer none): the #GObject to pass as data to @c_handler calls + * @flags: #GConnectFlags for the signal connection + * + * Connects @c_handler to the signal @detailed_signal on #GSignalGroup:target. + * + * Ensures that the @object stays alive during the call to @c_handler + * by temporarily adding a reference count. When the @object is destroyed + * the signal handler will automatically be removed. + * + * You cannot connect a signal handler after #GSignalGroup:target has been set. + * + * Since: 2.72 + */ +void +g_signal_group_connect_object (GSignalGroup *self, + const gchar *detailed_signal, + GCallback c_handler, + gpointer object, + GConnectFlags flags) +{ + g_return_if_fail (G_IS_OBJECT (object)); + + g_signal_group_connect_full (self, detailed_signal, c_handler, object, NULL, + flags, TRUE); +} + +/** + * g_signal_group_connect_data: + * @self: a #GSignalGroup + * @detailed_signal: a string of the form "signal-name::detail" + * @c_handler: (scope notified) (closure data) (destroy notify): the #GCallback to connect + * @data: the data to pass to @c_handler calls + * @notify: function to be called when disposing of @self + * @flags: the flags used to create the signal connection + * + * Connects @c_handler to the signal @detailed_signal + * on the target instance of @self. + * + * You cannot connect a signal handler after #GSignalGroup:target has been set. + * + * Since: 2.72 + */ +void +g_signal_group_connect_data (GSignalGroup *self, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data, + GClosureNotify notify, + GConnectFlags flags) +{ + g_signal_group_connect_full (self, detailed_signal, c_handler, data, notify, + flags, FALSE); +} + +/** + * g_signal_group_connect: (skip) + * @self: a #GSignalGroup + * @detailed_signal: a string of the form "signal-name::detail" + * @c_handler: (scope notified): the #GCallback to connect + * @data: the data to pass to @c_handler calls + * + * Connects @c_handler to the signal @detailed_signal + * on the target instance of @self. + * + * You cannot connect a signal handler after #GSignalGroup:target has been set. + * + * Since: 2.72 + */ +void +g_signal_group_connect (GSignalGroup *self, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data) +{ + g_signal_group_connect_full (self, detailed_signal, c_handler, data, NULL, + 0, FALSE); +} + +/** + * g_signal_group_connect_after: (skip) + * @self: a #GSignalGroup + * @detailed_signal: a string of the form "signal-name::detail" + * @c_handler: (scope notified): the #GCallback to connect + * @data: the data to pass to @c_handler calls + * + * Connects @c_handler to the signal @detailed_signal + * on the target instance of @self. + * + * The @c_handler will be called after the default handler of the signal. + * + * You cannot connect a signal handler after #GSignalGroup:target has been set. + * + * Since: 2.72 + */ +void +g_signal_group_connect_after (GSignalGroup *self, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data) +{ + g_signal_group_connect_full (self, detailed_signal, c_handler, + data, NULL, G_CONNECT_AFTER, FALSE); +} + +/** + * g_signal_group_connect_swapped: + * @self: a #GSignalGroup + * @detailed_signal: a string of the form "signal-name::detail" + * @c_handler: (scope async): the #GCallback to connect + * @data: the data to pass to @c_handler calls + * + * Connects @c_handler to the signal @detailed_signal + * on the target instance of @self. + * + * The instance on which the signal is emitted and @data + * will be swapped when calling @c_handler. + * + * You cannot connect a signal handler after #GSignalGroup:target has been set. + * + * Since: 2.72 + */ +void +g_signal_group_connect_swapped (GSignalGroup *self, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data) +{ + g_signal_group_connect_full (self, detailed_signal, c_handler, data, NULL, + G_CONNECT_SWAPPED, FALSE); +} diff --git a/gobject/gsignalgroup.h b/gobject/gsignalgroup.h new file mode 100644 index 000000000..c82a5cd4f --- /dev/null +++ b/gobject/gsignalgroup.h @@ -0,0 +1,93 @@ +/* GObject - GLib Type, Object, Parameter and Signal Library + * + * Copyright (C) 2015-2022 Christian Hergert <christian@hergert.me> + * Copyright (C) 2015 Garrett Regier <garrettregier@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef __G_SIGNAL_GROUP_H__ +#define __G_SIGNAL_GROUP_H__ + +#if !defined (__GLIB_GOBJECT_H_INSIDE__) && !defined (GOBJECT_COMPILATION) +#error "Only <glib-object.h> can be included directly." +#endif + +#include <glib.h> +#include <gobject/gobject.h> +#include <gobject/gsignal.h> + +G_BEGIN_DECLS + +#define G_SIGNAL_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_SIGNAL_GROUP, GSignalGroup)) +#define G_IS_SIGNAL_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_SIGNAL_GROUP)) +#define G_TYPE_SIGNAL_GROUP (g_signal_group_get_type()) + +/** + * GSignalGroup: + * + * #GSignalGroup is an opaque structure whose members + * cannot be accessed directly. + * + * Since: 2.72 + */ +typedef struct _GSignalGroup GSignalGroup; + +GLIB_AVAILABLE_IN_2_72 +GType g_signal_group_get_type (void) G_GNUC_CONST; +GLIB_AVAILABLE_IN_2_72 +GSignalGroup *g_signal_group_new (GType target_type); +GLIB_AVAILABLE_IN_2_72 +void g_signal_group_set_target (GSignalGroup *self, + gpointer target); +GLIB_AVAILABLE_IN_2_72 +gpointer g_signal_group_dup_target (GSignalGroup *self); +GLIB_AVAILABLE_IN_2_72 +void g_signal_group_block (GSignalGroup *self); +GLIB_AVAILABLE_IN_2_72 +void g_signal_group_unblock (GSignalGroup *self); +GLIB_AVAILABLE_IN_2_72 +void g_signal_group_connect_object (GSignalGroup *self, + const gchar *detailed_signal, + GCallback c_handler, + gpointer object, + GConnectFlags flags); +GLIB_AVAILABLE_IN_2_72 +void g_signal_group_connect_data (GSignalGroup *self, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data, + GClosureNotify notify, + GConnectFlags flags); +GLIB_AVAILABLE_IN_2_72 +void g_signal_group_connect (GSignalGroup *self, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data); +GLIB_AVAILABLE_IN_2_72 +void g_signal_group_connect_after (GSignalGroup *self, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data); +GLIB_AVAILABLE_IN_2_72 +void g_signal_group_connect_swapped (GSignalGroup *self, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data); + +G_END_DECLS + +#endif /* __G_SIGNAL_GROUP_H__ */ diff --git a/gobject/meson.build b/gobject/meson.build index 48bd87601..74feb09d9 100644 --- a/gobject/meson.build +++ b/gobject/meson.build @@ -11,6 +11,7 @@ gobject_install_headers = files( 'gparam.h', 'gparamspecs.h', 'gsignal.h', + 'gsignalgroup.h', 'gsourceclosure.h', 'gtype.h', 'gtypemodule.h', @@ -35,6 +36,7 @@ gobject_sources = files( 'gparam.c', 'gparamspecs.c', 'gsignal.c', + 'gsignalgroup.c', 'gsourceclosure.c', 'gtype.c', 'gtypemodule.c', diff --git a/gobject/tests/meson.build b/gobject/tests/meson.build index 901590471..8d568f00e 100644 --- a/gobject/tests/meson.build +++ b/gobject/tests/meson.build @@ -54,6 +54,7 @@ gobject_tests = { 'signals' : { 'source' : ['signals.c', marshalers_h, marshalers_c], }, + 'signalgroup' : {}, 'testing' : {}, 'type-flags' : {}, } diff --git a/gobject/tests/signalgroup.c b/gobject/tests/signalgroup.c new file mode 100644 index 000000000..5d1f17a81 --- /dev/null +++ b/gobject/tests/signalgroup.c @@ -0,0 +1,650 @@ +/* GObject - GLib Type, Object, Parameter and Signal Library + * + * Copyright (C) 2015-2022 Christian Hergert <christian@hergert.me> + * Copyright (C) 2015 Garrett Regier <garrettregier@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include <glib-object.h> + +G_DECLARE_FINAL_TYPE (SignalTarget, signal_target, TEST, SIGNAL_TARGET, GObject) + +struct _SignalTarget +{ + GObject parent_instance; +}; + +G_DEFINE_TYPE (SignalTarget, signal_target, G_TYPE_OBJECT) + +static G_DEFINE_QUARK (detail, signal_detail); + +enum { + THE_SIGNAL, + NEVER_EMITTED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +static void +signal_target_class_init (SignalTargetClass *klass) +{ + signals[THE_SIGNAL] = + g_signal_new ("the-signal", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); + + signals[NEVER_EMITTED] = + g_signal_new ("never-emitted", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); +} + +static void +signal_target_init (SignalTarget *self) +{ +} + +static gint global_signal_calls; +static gint global_weak_notify_called; + +static void +connect_before_cb (SignalTarget *target, + GSignalGroup *group, + gint *signal_calls) +{ + SignalTarget *readback; + + g_assert_true (TEST_IS_SIGNAL_TARGET (target)); + g_assert_true (G_IS_SIGNAL_GROUP (group)); + g_assert_nonnull (signal_calls); + g_assert_true (signal_calls == &global_signal_calls); + + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target); + g_object_unref (readback); + + *signal_calls += 1; +} + +static void +connect_after_cb (SignalTarget *target, + GSignalGroup *group, + gint *signal_calls) +{ + SignalTarget *readback; + + g_assert_true (TEST_IS_SIGNAL_TARGET (target)); + g_assert_true (G_IS_SIGNAL_GROUP (group)); + g_assert_nonnull (signal_calls); + g_assert_true (signal_calls == &global_signal_calls); + + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target); + g_object_unref (readback); + + g_assert_cmpint (*signal_calls, ==, 4); + *signal_calls += 1; +} + +static void +connect_swapped_cb (gint *signal_calls, + GSignalGroup *group, + SignalTarget *target) +{ + SignalTarget *readback; + + g_assert_true (signal_calls != NULL); + g_assert_true (signal_calls == &global_signal_calls); + g_assert_true (G_IS_SIGNAL_GROUP (group)); + g_assert_true (TEST_IS_SIGNAL_TARGET (target)); + + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target); + g_object_unref (readback); + + *signal_calls += 1; +} + +static void +connect_object_cb (SignalTarget *target, + GSignalGroup *group, + GObject *object) +{ + SignalTarget *readback; + gint *signal_calls; + + g_assert_true (TEST_IS_SIGNAL_TARGET (target)); + g_assert_true (G_IS_SIGNAL_GROUP (group)); + g_assert_true (G_IS_OBJECT (object)); + + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target); + g_object_unref (readback); + + signal_calls = g_object_get_data (object, "signal-calls"); + g_assert_nonnull (signal_calls); + g_assert_true (signal_calls == &global_signal_calls); + + *signal_calls += 1; +} + +static void +connect_bad_detail_cb (SignalTarget *target, + GSignalGroup *group, + GObject *object) +{ + g_error ("This detailed signal is never emitted!"); +} + +static void +connect_never_emitted_cb (SignalTarget *target, + gboolean *weak_notify_called) +{ + g_error ("This signal is never emitted!"); +} + +static void +connect_data_notify_cb (gboolean *weak_notify_called, + GClosure *closure) +{ + g_assert_nonnull (weak_notify_called); + g_assert_true (weak_notify_called == &global_weak_notify_called); + g_assert_nonnull (closure); + + g_assert_false (*weak_notify_called); + *weak_notify_called = TRUE; +} + +static void +connect_data_weak_notify_cb (gboolean *weak_notify_called, + GSignalGroup *group) +{ + g_assert_nonnull (weak_notify_called); + g_assert_true (weak_notify_called == &global_weak_notify_called); + g_assert_true (G_IS_SIGNAL_GROUP (group)); + + g_assert_true (*weak_notify_called); +} + +static void +connect_all_signals (GSignalGroup *group) +{ + GObject *object; + + /* Check that these are called in the right order */ + g_signal_group_connect (group, + "the-signal", + G_CALLBACK (connect_before_cb), + &global_signal_calls); + g_signal_group_connect_after (group, + "the-signal", + G_CALLBACK (connect_after_cb), + &global_signal_calls); + + /* Check that this is called with the arguments swapped */ + g_signal_group_connect_swapped (group, + "the-signal", + G_CALLBACK (connect_swapped_cb), + &global_signal_calls); + + /* Check that this is called with the arguments swapped */ + object = g_object_new (G_TYPE_OBJECT, NULL); + g_object_set_data (object, "signal-calls", &global_signal_calls); + g_signal_group_connect_object (group, + "the-signal", + G_CALLBACK (connect_object_cb), + object, + 0); + g_object_weak_ref (G_OBJECT (group), + (GWeakNotify)g_object_unref, + object); + + /* Check that a detailed signal is handled correctly */ + g_signal_group_connect (group, + "the-signal::detail", + G_CALLBACK (connect_before_cb), + &global_signal_calls); + g_signal_group_connect (group, + "the-signal::bad-detail", + G_CALLBACK (connect_bad_detail_cb), + NULL); + + /* Check that the notify is called correctly */ + global_weak_notify_called = FALSE; + g_signal_group_connect_data (group, + "never-emitted", + G_CALLBACK (connect_never_emitted_cb), + &global_weak_notify_called, + (GClosureNotify)connect_data_notify_cb, + 0); + g_object_weak_ref (G_OBJECT (group), + (GWeakNotify)connect_data_weak_notify_cb, + &global_weak_notify_called); +} + +static void +assert_signals (SignalTarget *target, + GSignalGroup *group, + gboolean success) +{ + g_assert (TEST_IS_SIGNAL_TARGET (target)); + g_assert (group == NULL || G_IS_SIGNAL_GROUP (group)); + + global_signal_calls = 0; + g_signal_emit (target, signals[THE_SIGNAL], + signal_detail_quark (), group); + g_assert_cmpint (global_signal_calls, ==, success ? 5 : 0); +} + +static void +dummy_handler (void) +{ +} + +static void +test_signal_group_invalid (void) +{ + GObject *invalid_target = g_object_new (G_TYPE_OBJECT, NULL); + SignalTarget *target = g_object_new (signal_target_get_type (), NULL); + GSignalGroup *group = g_signal_group_new (signal_target_get_type ()); + + /* Invalid Target Type */ + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*g_type_is_a*G_TYPE_OBJECT*"); + g_signal_group_new (G_TYPE_DATE_TIME); + g_test_assert_expected_messages (); + + /* Invalid Target */ + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*Failed to set GSignalGroup of target type SignalTarget using target * of type GObject*"); + g_signal_group_set_target (group, invalid_target); + g_assert_finalize_object (group); + g_test_assert_expected_messages (); + + /* Invalid Signal Name */ + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*g_signal_parse_name*"); + group = g_signal_group_new (signal_target_get_type ()); + g_signal_group_connect (group, + "does-not-exist", + G_CALLBACK (connect_before_cb), + NULL); + g_test_assert_expected_messages (); + g_assert_finalize_object (group); + + /* Invalid Callback */ + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*c_handler != NULL*"); + group = g_signal_group_new (signal_target_get_type ()); + g_signal_group_connect (group, + "the-signal", + G_CALLBACK (NULL), + NULL); + g_test_assert_expected_messages (); + g_assert_finalize_object (group); + + /* Connecting after setting target */ + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*Cannot add signals after setting target*"); + group = g_signal_group_new (signal_target_get_type ()); + g_signal_group_set_target (group, target); + g_signal_group_connect (group, + "the-signal", + G_CALLBACK (dummy_handler), + NULL); + g_test_assert_expected_messages (); + g_assert_finalize_object (group); + + g_assert_finalize_object (target); + g_assert_finalize_object (invalid_target); +} + +static void +test_signal_group_simple (void) +{ + SignalTarget *target; + GSignalGroup *group; + SignalTarget *readback; + + /* Set the target before connecting the signals */ + group = g_signal_group_new (signal_target_get_type ()); + target = g_object_new (signal_target_get_type (), NULL); + g_assert_null (g_signal_group_dup_target (group)); + g_signal_group_set_target (group, target); + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target); + g_object_unref (readback); + g_assert_finalize_object (group); + assert_signals (target, NULL, FALSE); + g_assert_finalize_object (target); + + group = g_signal_group_new (signal_target_get_type ()); + target = g_object_new (signal_target_get_type (), NULL); + connect_all_signals (group); + g_signal_group_set_target (group, target); + assert_signals (target, group, TRUE); + g_assert_finalize_object (target); + g_assert_finalize_object (group); +} + +static void +test_signal_group_changing_target (void) +{ + SignalTarget *target1, *target2; + GSignalGroup *group = g_signal_group_new (signal_target_get_type ()); + SignalTarget *readback; + + connect_all_signals (group); + g_assert_null (g_signal_group_dup_target (group)); + + /* Set the target after connecting the signals */ + target1 = g_object_new (signal_target_get_type (), NULL); + g_signal_group_set_target (group, target1); + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target1); + g_object_unref (readback); + + assert_signals (target1, group, TRUE); + + /* Set the same target */ + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target1); + g_object_unref (readback); + g_signal_group_set_target (group, target1); + + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target1); + g_object_unref (readback); + + assert_signals (target1, group, TRUE); + + /* Set a new target when the current target is non-NULL */ + target2 = g_object_new (signal_target_get_type (), NULL); + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target1); + g_object_unref (readback); + + g_signal_group_set_target (group, target2); + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target2); + g_object_unref (readback); + + assert_signals (target2, group, TRUE); + + g_assert_finalize_object (target2); + g_assert_finalize_object (target1); + g_assert_finalize_object (group); +} + +static void +assert_blocking (SignalTarget *target, + GSignalGroup *group, + gint count) +{ + gint i; + + assert_signals (target, group, TRUE); + + /* Assert that multiple blocks are effective */ + for (i = 0; i < count; ++i) + { + g_signal_group_block (group); + assert_signals (target, group, FALSE); + } + + /* Assert that the signal is not emitted after the first unblock */ + for (i = 0; i < count; ++i) + { + assert_signals (target, group, FALSE); + g_signal_group_unblock (group); + } + + assert_signals (target, group, TRUE); +} + +static void +test_signal_group_blocking (void) +{ + SignalTarget *target1, *target2, *readback; + GSignalGroup *group = g_signal_group_new (signal_target_get_type ()); + + /* Test blocking and unblocking null target */ + g_signal_group_block (group); + g_signal_group_unblock (group); + + connect_all_signals (group); + g_assert_null (g_signal_group_dup_target (group)); + + target1 = g_object_new (signal_target_get_type (), NULL); + g_signal_group_set_target (group, target1); + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target1); + g_object_unref (readback); + + assert_blocking (target1, group, 1); + assert_blocking (target1, group, 3); + assert_blocking (target1, group, 15); + + /* Assert that blocking transfers across changing the target */ + g_signal_group_block (group); + g_signal_group_block (group); + + /* Set a new target when the current target is non-NULL */ + target2 = g_object_new (signal_target_get_type (), NULL); + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target1); + g_object_unref (readback); + g_signal_group_set_target (group, target2); + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target2); + g_object_unref (readback); + + assert_signals (target2, group, FALSE); + g_signal_group_unblock (group); + assert_signals (target2, group, FALSE); + g_signal_group_unblock (group); + assert_signals (target2, group, TRUE); + + g_assert_finalize_object (target2); + g_assert_finalize_object (target1); + g_assert_finalize_object (group); +} + +static void +test_signal_group_weak_ref_target (void) +{ + SignalTarget *target = g_object_new (signal_target_get_type (), NULL); + GSignalGroup *group = g_signal_group_new (signal_target_get_type ()); + SignalTarget *readback; + + g_assert_null (g_signal_group_dup_target (group)); + g_signal_group_set_target (group, target); + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target); + g_object_unref (readback); + + g_assert_finalize_object (target); + g_assert_null (g_signal_group_dup_target (group)); + g_assert_finalize_object (group); +} + +static void +test_signal_group_connect_object (void) +{ + GObject *object = g_object_new (G_TYPE_OBJECT, NULL); + SignalTarget *target = g_object_new (signal_target_get_type (), NULL); + GSignalGroup *group = g_signal_group_new (signal_target_get_type ()); + SignalTarget *readback; + + /* We already do basic connect_object() tests in connect_signals(), + * this is only needed to test the specifics of connect_object() + */ + g_signal_group_connect_object (group, + "the-signal", + G_CALLBACK (connect_object_cb), + object, + 0); + + g_assert_null (g_signal_group_dup_target (group)); + g_signal_group_set_target (group, target); + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target); + g_object_unref (readback); + + g_assert_finalize_object (object); + + /* This would cause a warning if the SignalGroup did not + * have a weakref on the object as it would try to connect again + */ + g_signal_group_set_target (group, NULL); + g_assert_null (g_signal_group_dup_target (group)); + g_signal_group_set_target (group, target); + readback = g_signal_group_dup_target (group); + g_assert_true (readback == target); + g_object_unref (readback); + + g_assert_finalize_object (group); + g_assert_finalize_object (target); +} + +static void +test_signal_group_signal_parsing (void) +{ + g_test_trap_subprocess ("/GObject/SignalGroup/signal-parsing/subprocess", 0, + G_TEST_SUBPROCESS_INHERIT_STDERR); + g_test_trap_assert_passed (); + g_test_trap_assert_stderr (""); +} + +static void +test_signal_group_signal_parsing_subprocess (void) +{ + GSignalGroup *group; + + /* Check that the class has not been created and with it the + * signals registered. This will cause g_signal_parse_name() + * to fail unless GSignalGroup calls g_type_class_ref(). + */ + g_assert_null (g_type_class_peek (signal_target_get_type ())); + + group = g_signal_group_new (signal_target_get_type ()); + g_signal_group_connect (group, + "the-signal", + G_CALLBACK (connect_before_cb), + NULL); + + g_assert_finalize_object (group); +} + +static void +test_signal_group_properties (void) +{ + GSignalGroup *group; + SignalTarget *target, *other; + GType gtype; + + group = g_signal_group_new (signal_target_get_type ()); + g_object_get (group, + "target", &target, + "target-type", >ype, + NULL); + g_assert_cmpint (gtype, ==, signal_target_get_type ()); + g_assert_null (target); + + target = g_object_new (signal_target_get_type (), NULL); + g_object_set (group, "target", target, NULL); + g_object_get (group, "target", &other, NULL); + g_assert_true (target == other); + g_object_unref (other); + + g_assert_finalize_object (target); + g_assert_null (g_signal_group_dup_target (group)); + g_assert_finalize_object (group); +} + +G_DECLARE_INTERFACE (SignalThing, signal_thing, SIGNAL, THING, GObject) + +struct _SignalThingInterface +{ + GTypeInterface iface; + void (*changed) (SignalThing *thing); +}; + +G_DEFINE_INTERFACE (SignalThing, signal_thing, G_TYPE_OBJECT) + +static guint signal_thing_changed; + +static void +signal_thing_default_init (SignalThingInterface *iface) +{ + signal_thing_changed = + g_signal_new ("changed", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (SignalThingInterface, changed), + NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + +G_GNUC_NORETURN static void +thing_changed_cb (SignalThing *thing, + gpointer user_data G_GNUC_UNUSED) +{ + g_assert_not_reached (); +} + +static void +test_signal_group_interface (void) +{ + GSignalGroup *group; + + group = g_signal_group_new (signal_thing_get_type ()); + g_signal_group_connect (group, + "changed", + G_CALLBACK (thing_changed_cb), + NULL); + g_assert_finalize_object (group); +} + +gint +main (gint argc, + gchar *argv[]) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/GObject/SignalGroup/invalid", test_signal_group_invalid); + g_test_add_func ("/GObject/SignalGroup/simple", test_signal_group_simple); + g_test_add_func ("/GObject/SignalGroup/changing-target", test_signal_group_changing_target); + g_test_add_func ("/GObject/SignalGroup/blocking", test_signal_group_blocking); + g_test_add_func ("/GObject/SignalGroup/weak-ref-target", test_signal_group_weak_ref_target); + g_test_add_func ("/GObject/SignalGroup/connect-object", test_signal_group_connect_object); + g_test_add_func ("/GObject/SignalGroup/signal-parsing", test_signal_group_signal_parsing); + g_test_add_func ("/GObject/SignalGroup/signal-parsing/subprocess", test_signal_group_signal_parsing_subprocess); + g_test_add_func ("/GObject/SignalGroup/properties", test_signal_group_properties); + g_test_add_func ("/GObject/SignalGroup/interface", test_signal_group_interface); + return g_test_run (); +} |