[gnome-builder] egg-signal-group: add EggSignalGroup helper



commit db790bb872fa2d5dc6b81dd07678e6931db238a8
Author: Christian Hergert <christian hergert me>
Date:   Fri May 1 15:18:18 2015 -0700

    egg-signal-group: add EggSignalGroup helper
    
    This is a work in progress to simplify connecting to a group of signals
    on a particular object that may change during runtime. Things like
    signal blocking will be managed and transfered to the new object.
    
    I expect this to go through a bunch of iterations as we use it to
    simplify code in modules like ide-source-view.

 configure.ac                   |    4 +-
 contrib/Makefile.am            |    2 +-
 contrib/egg/Makefile.am        |   11 +
 contrib/egg/egg-signal-group.c |  414 ++++++++++++++++++++++++++++++++++++++++
 contrib/egg/egg-signal-group.h |   44 +++++
 libide/Makefile.am             |    4 +-
 6 files changed, 475 insertions(+), 4 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index b412b43..09f19bf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -125,7 +125,7 @@ PKG_CHECK_MODULES(BUILDER, [gtk+-3.0 >= gtk_required_version
                             gtksourceview-3.0 >= gtksourceview_required_version
                             libdevhelp-3.0 >= devhelp_required_version
                             libgit2-glib-1.0 >= ggit_required_version])
-
+PKG_CHECK_MODULES(EGG,     [glib-2.0 >= glib_required_version])
 PKG_CHECK_MODULES(LIBIDE,  [gio-2.0 >= glib_required_version
                             gio-unix-2.0 >= glib_required_version
                             gtksourceview-3.0 >= gtksourceview_required_version
@@ -134,7 +134,6 @@ PKG_CHECK_MODULES(LIBIDE,  [gio-2.0 >= glib_required_version
                             gjs-1.0 >= gjs_required_version
                             gjs-internals-1.0 >= gjs_required_version
                             pygobject-3.0 >= pygobject_required_version])
-
 PKG_CHECK_MODULES(SEARCH,  [glib-2.0 >= glib_required_version])
 
 
@@ -288,6 +287,7 @@ AC_CONFIG_FILES([
        build/autotools/Makefile
 
        contrib/Makefile
+       contrib/egg/Makefile
        contrib/search/Makefile
        contrib/libeditorconfig/Makefile
 
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
index f2f9919..f1d684f 100644
--- a/contrib/Makefile.am
+++ b/contrib/Makefile.am
@@ -1,3 +1,3 @@
-SUBDIRS = search libeditorconfig
+SUBDIRS = search libeditorconfig egg
 
 -include $(top_srcdir)/git.mk
diff --git a/contrib/egg/Makefile.am b/contrib/egg/Makefile.am
new file mode 100644
index 0000000..fa8a0ee
--- /dev/null
+++ b/contrib/egg/Makefile.am
@@ -0,0 +1,11 @@
+noinst_LTLIBRARIES = libegg.la
+
+libegg_la_SOURCES = \
+       egg-signal-group.c \
+       egg-signal-group.h \
+       $(NULL)
+
+libegg_la_CFLAGS = $(EGG_CFLAGS)
+libegg_la_LIBADD = $(EGG_LIBS)
+
+-include $(top_srcdir)/git.mk
diff --git a/contrib/egg/egg-signal-group.c b/contrib/egg/egg-signal-group.c
new file mode 100644
index 0000000..dfd726d
--- /dev/null
+++ b/contrib/egg/egg-signal-group.c
@@ -0,0 +1,414 @@
+/* egg-signal-group.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file 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 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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 General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "egg-signal-group"
+
+#include <glib/gi18n.h>
+
+#include "egg-signal-group.h"
+
+struct _EggSignalGroup
+{
+  GObject    parent_instance;
+
+  GObject   *target;
+  GPtrArray *handlers;
+  GType      target_type;
+  gint       block_count;
+};
+
+struct _EggSignalGroupClass
+{
+  GObjectClass parent_class;
+
+  void (*bind)   (EggSignalGroup *self,
+                  GObject        *target);
+  void (*unbind) (EggSignalGroup *self,
+                  GObject        *target);
+};
+
+typedef struct
+{
+  gulong       handler_id;
+  GClosure    *closure;
+  const gchar *detailed_signal;
+  guint        connect_after : 1;
+} SignalHandler;
+
+G_DEFINE_TYPE (EggSignalGroup, egg_signal_group, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_TARGET,
+  PROP_TARGET_TYPE,
+  LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+static void
+egg_signal_group__target_weak_notify (gpointer  data,
+                                      GObject  *where_object_was)
+{
+  EggSignalGroup *self = data;
+  gsize i;
+
+  g_assert (EGG_IS_SIGNAL_GROUP (self));
+  g_assert (where_object_was != NULL);
+  g_assert (self->target == where_object_was);
+
+  for (i = 0; i < self->handlers->len; i++)
+    {
+      SignalHandler *handler;
+
+      handler = g_ptr_array_index (self->handlers, i);
+      handler->handler_id = 0;
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_TARGET]);
+}
+
+static void
+egg_signal_group_bind (EggSignalGroup *self,
+                       GObject        *target)
+{
+  g_assert (EGG_IS_SIGNAL_GROUP (self));
+  g_assert (self->target == NULL);
+  g_assert (!target || G_IS_OBJECT (target));
+
+  if (target != NULL)
+    {
+      gsize i;
+
+      self->target = target;
+      g_object_weak_ref (self->target,
+                         egg_signal_group__target_weak_notify,
+                         self);
+
+      for (i = 0; i < self->handlers->len; i++)
+        {
+          SignalHandler *handler;
+          gint j;
+
+          handler = g_ptr_array_index (self->handlers, i);
+
+          g_assert (handler != NULL);
+          g_assert (handler->detailed_signal != NULL);
+          g_assert (handler->closure != NULL);
+          g_assert_cmpint (handler->handler_id, ==, 0);
+
+          handler->handler_id =  g_signal_connect_closure (target,
+                                                           handler->detailed_signal,
+                                                           handler->closure,
+                                                           handler->connect_after);
+
+          for (j = 0; j < self->block_count; j++)
+            g_signal_handler_block (target, handler->handler_id);
+        }
+    }
+}
+
+static void
+egg_signal_group_unbind (EggSignalGroup *self)
+{
+  g_return_if_fail (EGG_IS_SIGNAL_GROUP (self));
+
+  if (self->target != NULL)
+    {
+      GObject *target;
+      gsize i;
+
+      target = self->target;
+      self->target = NULL;
+
+      g_object_weak_unref (target,
+                           egg_signal_group__target_weak_notify,
+                           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->detailed_signal != NULL);
+          g_assert (handler->closure != NULL);
+          g_assert_cmpint (handler->handler_id, !=, 0);
+
+          handler_id = handler->handler_id;
+          handler->handler_id = 0;
+
+          g_signal_handler_disconnect (target, handler_id);
+        }
+    }
+}
+
+void
+egg_signal_group_block (EggSignalGroup *self)
+{
+  g_return_if_fail (EGG_IS_SIGNAL_GROUP (self));
+
+  self->block_count++;
+
+  if (self->target != NULL)
+    {
+      gsize i;
+
+      for (i = 0; i < self->handlers->len; i++)
+        {
+          SignalHandler *handler;
+
+          handler = g_ptr_array_index (self->handlers, i);
+
+          g_assert (handler != NULL);
+          g_assert (handler->detailed_signal != NULL);
+          g_assert (handler->closure != NULL);
+          g_assert_cmpint (handler->handler_id, !=, 0);
+
+          g_signal_handler_block (self->target, handler->handler_id);
+        }
+    }
+}
+
+void
+egg_signal_group_unblock (EggSignalGroup *self)
+{
+  g_return_if_fail (EGG_IS_SIGNAL_GROUP (self));
+
+  self->block_count--;
+
+  if (self->target != NULL)
+    {
+      gsize i;
+
+      for (i = 0; i < self->handlers->len; i++)
+        {
+          SignalHandler *handler;
+
+          handler = g_ptr_array_index (self->handlers, i);
+
+          g_assert (handler != NULL);
+          g_assert (handler->detailed_signal != NULL);
+          g_assert (handler->closure != NULL);
+          g_assert_cmpint (handler->handler_id, !=, 0);
+
+          g_signal_handler_unblock (self->target, handler->handler_id);
+        }
+    }
+}
+
+/**
+ * egg_signal_group_get_target:
+ *
+ * Gets the target instance for the signal group.
+ *
+ * All signals that are registered will be connected
+ * or disconnected when this property changes.
+ *
+ * Returns: (transfer none): The #EggSignalGroup:target property.
+ */
+gpointer
+egg_signal_group_get_target (EggSignalGroup *self)
+{
+  g_return_val_if_fail (EGG_IS_SIGNAL_GROUP (self), NULL);
+
+  return (gpointer)self->target;
+}
+
+/**
+ * egg_signal_group_set_target:
+ * @self: An #EggSignalGroup.
+ * @target: (nullable) (ctype GObject*): The instance for which to connect signals.
+ *
+ * Sets the target instance to connect signals to. Any signal that has been registered
+ * with egg_signal_group_connect_object() or similar functions will be connected to this
+ * object.
+ *
+ * If #EggSignalGroup:target was previously set, signals will be disconnected from that
+ * object prior to connecting to this object.
+ */
+void
+egg_signal_group_set_target (EggSignalGroup *self,
+                             gpointer        target)
+{
+  g_return_if_fail (EGG_IS_SIGNAL_GROUP (self));
+  g_return_if_fail (!target || G_IS_OBJECT (target));
+
+  /*
+   * We cannot give meaningful warnings if EggSignalGroup:target is set before
+   * EggSignalGroup:target-type. We might be able to delay some of this until
+   * constructed, but that is more effort than it is worth right now.
+   */
+  if ((target != NULL) &&
+      (self->target_type != G_TYPE_NONE) &&
+      !g_type_is_a (G_OBJECT_TYPE (target), self->target_type))
+    {
+      g_warning ("Attempt to set EggSignalGroup:target to something other than %s",
+                 g_type_name (self->target_type));
+      return;
+    }
+
+  if (target != self->target)
+    {
+      egg_signal_group_unbind (self);
+      egg_signal_group_bind (self, target);
+      g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_TARGET]);
+    }
+}
+
+static void
+signal_handler_free (gpointer data)
+{
+  SignalHandler *handler = data;
+
+  if (handler != NULL)
+    {
+      g_clear_pointer (&handler->closure, g_closure_unref);
+      handler->handler_id = 0;
+      handler->detailed_signal = NULL;
+      handler->connect_after = FALSE;
+      g_slice_free (SignalHandler, handler);
+    }
+}
+
+static void
+egg_signal_group_dispose (GObject *object)
+{
+  EggSignalGroup *self = (EggSignalGroup *)object;
+
+  egg_signal_group_unbind (self);
+  g_clear_pointer (&self->handlers, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (egg_signal_group_parent_class)->dispose (object);
+}
+
+static void
+egg_signal_group_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  EggSignalGroup *self = EGG_SIGNAL_GROUP (object);
+
+  switch (prop_id)
+    {
+    case PROP_TARGET:
+      g_value_set_object (value, egg_signal_group_get_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
+egg_signal_group_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  EggSignalGroup *self = EGG_SIGNAL_GROUP (object);
+
+  switch (prop_id)
+    {
+    case PROP_TARGET:
+      egg_signal_group_set_target (self, g_value_get_object (value));
+      break;
+
+    case PROP_TARGET_TYPE:
+      self->target_type = g_value_get_gtype (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+egg_signal_group_class_init (EggSignalGroupClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = egg_signal_group_dispose;
+  object_class->get_property = egg_signal_group_get_property;
+  object_class->set_property = egg_signal_group_set_property;
+
+  gParamSpecs [PROP_TARGET] =
+    g_param_spec_object ("target",
+                         _("Target"),
+                         _("The target instance for which to connect signals."),
+                         G_TYPE_OBJECT,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_TARGET, gParamSpecs [PROP_TARGET]);
+
+  gParamSpecs [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_property (object_class, PROP_TARGET_TYPE, gParamSpecs [PROP_TARGET_TYPE]);
+}
+
+static void
+egg_signal_group_init (EggSignalGroup *self)
+{
+  self->handlers = g_ptr_array_new_with_free_func (signal_handler_free);
+  self->target_type = G_TYPE_NONE;
+}
+
+EggSignalGroup *
+egg_signal_group_new (GType target_type)
+{
+  return g_object_new (EGG_TYPE_SIGNAL_GROUP,
+                       "target-type", target_type,
+                       NULL);
+}
+
+void
+egg_signal_group_connect_object (EggSignalGroup *self,
+                                 const gchar    *detailed_signal,
+                                 GCallback       callback,
+                                 gpointer        data,
+                                 GSignalFlags    flags)
+{
+  SignalHandler *handler;
+  GClosure *closure;
+
+  g_return_val_if_fail (EGG_IS_SIGNAL_GROUP (self), 0);
+  g_return_val_if_fail (detailed_signal != NULL, 0);
+  g_return_val_if_fail (callback != NULL, 0);
+
+  if ((flags & G_CONNECT_SWAPPED) != 0)
+    closure = g_cclosure_new_object_swap (callback, data);
+  else
+    closure = g_cclosure_new_object (callback, data);
+
+  handler = g_slice_new0 (SignalHandler);
+  handler->detailed_signal = g_intern_string (detailed_signal);
+  handler->closure = closure;
+  handler->connect_after = ((flags & G_CONNECT_AFTER) != 0);
+
+  g_ptr_array_add (self->handlers, handler);
+}
diff --git a/contrib/egg/egg-signal-group.h b/contrib/egg/egg-signal-group.h
new file mode 100644
index 0000000..3e42aa4
--- /dev/null
+++ b/contrib/egg/egg-signal-group.h
@@ -0,0 +1,44 @@
+/* egg-signal-group.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file 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 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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 General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EGG_SIGNAL_GROUP_H
+#define EGG_SIGNAL_GROUP_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_SIGNAL_GROUP (egg_signal_group_get_type())
+
+G_DECLARE_FINAL_TYPE (EggSignalGroup, egg_signal_group, EGG, SIGNAL_GROUP, GObject)
+
+EggSignalGroup *egg_signal_group_new            (GType           target_type);
+void            egg_signal_group_connect_object (EggSignalGroup *self,
+                                                 const gchar    *detailed_signal,
+                                                 GCallback       c_handler,
+                                                 gpointer        data,
+                                                 GSignalFlags    flags);
+void            egg_signal_group_set_target     (EggSignalGroup *self,
+                                                 gpointer        target);
+gpointer        egg_signal_group_get_target     (EggSignalGroup *self);
+void            egg_signal_group_block          (EggSignalGroup *self);
+void            egg_signal_group_unblock        (EggSignalGroup *self);
+
+G_END_DECLS
+
+#endif /* EGG_SIGNAL_GROUP_H */
diff --git a/libide/Makefile.am b/libide/Makefile.am
index b32fe80..269aed2 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -309,8 +309,9 @@ libide_1_0_la_includes = \
        $(DEBUG_CFLAGS) \
        $(CLANG_CFLAGS) \
        -I$(top_builddir)/libide \
-       -I$(top_srcdir)/contrib/search \
+       -I$(top_srcdir)/contrib/egg \
        -I$(top_srcdir)/contrib/libeditorconfig \
+       -I$(top_srcdir)/contrib/search \
        -I$(srcdir) \
        -I$(srcdir)/autotools \
        -I$(srcdir)/c \
@@ -368,6 +369,7 @@ libide_1_0_la_LIBADD = \
        $(LIBIDE_LIBS) \
        -lclang \
        -lm \
+       $(top_builddir)/contrib/egg/libegg.la \
        $(top_builddir)/contrib/libeditorconfig/libeditorconfig.la \
        $(top_builddir)/contrib/search/libsearch.la \
        $(NULL)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]