[gnome-bluetooth] Add seahorse-bind (from Seahorse) for binding GObject properties



commit 5a8246e29d22049ee1a56015213dd6dcebc3dedd
Author: Joshua Lock <josh linux intel com>
Date:   Fri Sep 25 11:51:19 2009 +0100

    Add seahorse-bind (from Seahorse) for binding GObject properties
    
    Hopefully we can remove this once GObject integrates such functionality
    as tracked in BGO#348080 https://bugzilla.gnome.org/show_bug.cgi?id=348080
    
    https://bugzilla.gnome.org/show_bug.cgi?id=596294

 lib/Makefile.am     |    1 +
 lib/seahorse-bind.c |  555 +++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/seahorse-bind.h |   43 ++++
 3 files changed, 599 insertions(+), 0 deletions(-)
---
diff --git a/lib/Makefile.am b/lib/Makefile.am
index f4dea76..4877afe 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -31,6 +31,7 @@ libgnome_bluetooth_la_SOURCES =				\
 	bling-spinner.c bling-spinner.h			\
 	bluetooth-chooser.c bluetooth-chooser.h		\
 	bluetooth-chooser-private.h			\
+	seahorse-bind.c seahorse-bind.h 		\
 	bluetooth-chooser-button.c bluetooth-chooser-button.h
 
 libgnome_bluetooth_la_LIBADD = $(LIBGNOMEBT_LIBS)
diff --git a/lib/seahorse-bind.c b/lib/seahorse-bind.c
new file mode 100644
index 0000000..b25f20b
--- /dev/null
+++ b/lib/seahorse-bind.c
@@ -0,0 +1,555 @@
+/* 
+ * Seahorse
+ * 
+ * Copyright (C) 2008 Stefan Walter
+ * 
+ * This program 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 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
+ * Lesser General Public License for more details.
+ *  
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.  
+ */
+   
+#include "config.h"
+
+#include "seahorse-bind.h"
+
+#include <string.h>
+
+static gboolean
+value_equal (const GValue *a, const GValue *b)
+{
+	gboolean retval;
+	const gchar *stra, *strb;
+
+	if (G_VALUE_TYPE (a) != G_VALUE_TYPE (b))
+		return FALSE;
+	
+	switch (G_VALUE_TYPE (a))
+	{
+	case G_TYPE_BOOLEAN:
+		if (g_value_get_boolean (a) < g_value_get_boolean (b))
+			retval = FALSE;
+		else if (g_value_get_boolean (a) == g_value_get_boolean (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_CHAR:
+		if (g_value_get_char (a) < g_value_get_char (b))
+			retval = FALSE;
+		else if (g_value_get_char (a) == g_value_get_char (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_UCHAR:
+		if (g_value_get_uchar (a) < g_value_get_uchar (b))
+			retval = FALSE;
+		else if (g_value_get_uchar (a) == g_value_get_uchar (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_INT:
+		if (g_value_get_int (a) < g_value_get_int (b))
+			retval = FALSE;
+		else if (g_value_get_int (a) == g_value_get_int (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_UINT:
+		if (g_value_get_uint (a) < g_value_get_uint (b))
+			retval = FALSE;
+		else if (g_value_get_uint (a) == g_value_get_uint (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_LONG:
+		if (g_value_get_long (a) < g_value_get_long (b))
+			retval = FALSE;
+		else if (g_value_get_long (a) == g_value_get_long (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_ULONG:
+		if (g_value_get_ulong (a) < g_value_get_ulong (b))
+			retval = FALSE;
+		else if (g_value_get_ulong (a) == g_value_get_ulong (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_INT64:
+		if (g_value_get_int64 (a) < g_value_get_int64 (b))
+			retval = FALSE;
+		else if (g_value_get_int64 (a) == g_value_get_int64 (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_UINT64:
+		if (g_value_get_uint64 (a) < g_value_get_uint64 (b))
+			retval = FALSE;
+		else if (g_value_get_uint64 (a) == g_value_get_uint64 (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_ENUM:
+		/* this is somewhat bogus. */
+		if (g_value_get_enum (a) < g_value_get_enum (b))
+			retval = FALSE;
+		else if (g_value_get_enum (a) == g_value_get_enum (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_FLAGS:
+		/* this is even more bogus. */
+		if (g_value_get_flags (a) < g_value_get_flags (b))
+			retval = FALSE;
+		else if (g_value_get_flags (a) == g_value_get_flags (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_FLOAT:
+		if (g_value_get_float (a) < g_value_get_float (b))
+			retval = FALSE;
+		else if (g_value_get_float (a) == g_value_get_float (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_DOUBLE:
+		if (g_value_get_double (a) < g_value_get_double (b))
+			retval = FALSE;
+		else if (g_value_get_double (a) == g_value_get_double (b))
+			retval = TRUE;
+		else
+			retval = FALSE;
+		break;
+	case G_TYPE_STRING:
+		stra = g_value_get_string (a);
+		strb = g_value_get_string (b);
+		if (stra == strb)
+			retval = TRUE;
+		else if (!stra || !strb)
+			retval = FALSE;
+		else
+			retval = g_utf8_collate (stra, strb) == 0;
+		break;
+	case G_TYPE_POINTER:
+		retval = (g_value_get_pointer (a) == g_value_get_pointer (b));
+		break;
+	case G_TYPE_BOXED:
+		retval = (g_value_get_boxed (a) == g_value_get_boxed (b));
+		break;
+	case G_TYPE_OBJECT:
+		retval = (g_value_get_object (a) == g_value_get_object (b));
+		break;
+	default:
+		/* Default case is not equal */
+		retval = FALSE;
+		break;
+	}
+	
+	return retval;
+}
+
+static GHashTable *all_bindings = NULL;
+
+typedef struct _Binding {
+	GObject *obj_src;
+	GParamSpec *prop_src;
+	GParamSpec *prop_dest;
+	GSList *obj_dests;
+	
+	gulong connection;
+	SeahorseTransform transform;
+	gboolean processing;
+	
+	gint references;
+} Binding;
+
+static void binding_unref (Binding *binding);
+static void binding_ref (Binding *binding);
+
+static void 
+binding_src_gone (gpointer data, GObject *was)
+{
+	Binding *binding = (Binding*)data;
+	g_assert (binding->obj_src == was);
+	binding->obj_src = NULL;
+	binding_unref (binding);
+}
+
+static void
+binding_dest_gone (gpointer data, GObject *was)
+{
+	Binding *binding = (Binding*)data;
+	GSList *at;
+
+	at = g_slist_find (binding->obj_dests, was);
+	g_assert (at != NULL);
+	
+	/* Remove it from the list */
+	binding->obj_dests = g_slist_delete_link (binding->obj_dests, at);
+	
+	/* If no more destination objects, then go away */
+	if (!binding->obj_dests)
+		binding_unref (binding);
+}
+
+static void 
+binding_ref (Binding *binding)
+{
+	g_assert (binding);
+	++binding->references;
+}
+
+static void
+binding_unref (Binding *binding)
+{
+	GSList *l;
+
+	g_assert (binding);
+	
+	g_assert (binding->references > 0);
+	--binding->references;
+	if (binding->references > 0)
+		return;
+	
+	if (G_IS_OBJECT (binding->obj_src)) {
+		g_signal_handler_disconnect (binding->obj_src, binding->connection);
+		g_object_weak_unref (binding->obj_src, binding_src_gone, binding);
+		binding->obj_src = NULL;
+	}
+	
+	for (l = binding->obj_dests; l; l = g_slist_next (l)) {
+		if (G_IS_OBJECT (l->data))
+			g_object_weak_unref (l->data, binding_dest_gone, binding);
+	}
+	g_slist_free (binding->obj_dests);
+	binding->obj_dests = NULL;
+	
+	g_assert (binding->prop_src);
+	g_param_spec_unref (binding->prop_src);
+	binding->prop_src = NULL;
+	
+	g_assert (binding->prop_dest);
+	g_param_spec_unref (binding->prop_dest);
+	binding->prop_dest = NULL;
+	
+	g_free (binding);
+	
+	/* Remove from the list of all bindings */
+	g_assert (all_bindings);
+	g_hash_table_remove (all_bindings, binding);
+	if (g_hash_table_size (all_bindings) == 0) {
+		g_hash_table_destroy (all_bindings);
+		all_bindings = NULL;
+	}
+}
+
+static void
+bind_transfer (Binding *binding, gboolean forward)
+{
+	GValue src, dest, check;
+	GSList *l;
+	
+	g_assert (binding->obj_src);
+	g_assert (binding->obj_dests);
+	
+	/* IMPORTANT: No return during this fuction */
+	binding_ref (binding);
+	binding->processing = TRUE;
+
+	/* Get the value from the source object */
+	memset (&src, 0, sizeof (src));
+	g_value_init (&src, binding->prop_src->value_type);
+	g_object_get_property (binding->obj_src, binding->prop_src->name, &src);
+	
+	/* Transform the value */
+	memset (&dest, 0, sizeof (dest));
+	g_value_init (&dest, binding->prop_dest->value_type);
+	if ((binding->transform) (&src, &dest)) {
+
+
+		for (l = binding->obj_dests; l; l = g_slist_next (l)) {
+
+			/* Get the current value of the destination object */
+			memset (&check, 0, sizeof (check));
+			g_value_init (&check, binding->prop_dest->value_type);
+			g_object_get_property (l->data, binding->prop_dest->name, &check);
+			
+			/* Set the property on the destination object */
+			if (!value_equal (&dest, &check))
+				g_object_set_property (l->data, binding->prop_dest->name, &dest);
+
+			g_value_unset (&check);
+		}
+
+	} else {
+		
+		g_warning ("couldn't transform value from '%s' to '%s' when trying to "
+		           "transfer bound property from %s.%s to %s.%s",
+		           g_type_name (binding->prop_src->value_type), 
+		           g_type_name (binding->prop_dest->value_type),
+		           G_OBJECT_CLASS_NAME (G_OBJECT_GET_CLASS (binding->obj_src)), 
+		           binding->prop_src->name,
+		           G_OBJECT_CLASS_NAME (G_OBJECT_GET_CLASS (binding->obj_dests->data)), 
+		           binding->prop_dest->name);
+	}
+	
+	g_value_unset (&src);
+	g_value_unset (&dest);
+	
+	binding->processing = FALSE;
+	binding_unref (binding);
+}
+
+static void 
+binding_fire (GObject *gobject, GParamSpec *pspec, gpointer data)
+{
+	Binding *binding = (Binding*)data;
+	g_assert (gobject == binding->obj_src);
+	g_assert (pspec == binding->prop_src);
+	g_assert (binding->transform);
+	
+	if (!binding->processing)
+		bind_transfer (binding, TRUE);
+}
+
+gpointer 
+seahorse_bind_property (const gchar *prop_src, gpointer obj_src, 
+                        const gchar *prop_dest, gpointer obj_dest)
+{
+	g_return_val_if_fail (G_IS_OBJECT (obj_src), NULL);
+	g_return_val_if_fail (prop_src, NULL);
+	g_return_val_if_fail (G_IS_OBJECT (obj_dest), NULL);
+	g_return_val_if_fail (prop_dest, NULL);
+	
+	return seahorse_bind_property_full (prop_src, obj_src,
+	                                    g_value_transform,
+	                                    prop_dest, obj_dest, NULL);
+}
+
+gpointer 
+seahorse_bind_property_full (const gchar *prop_src, gpointer obj_src,
+                             SeahorseTransform transform,
+                             const gchar *prop_dest, ...)
+{
+	GObjectClass *cls;
+	GParamSpec *spec_src;
+	GParamSpec *spec_dest;
+	GParamSpec *spec;
+	Binding *binding;
+	gchar *detail;
+	GObject *dest;
+	GSList *dests, *l;
+	va_list va;
+
+	g_return_val_if_fail (transform, NULL);
+	g_return_val_if_fail (G_IS_OBJECT (obj_src), NULL);
+	g_return_val_if_fail (prop_src, NULL);
+	g_return_val_if_fail (prop_dest, NULL);
+	
+	cls = G_OBJECT_GET_CLASS (obj_src);
+	spec_src = g_object_class_find_property (cls, prop_src);
+	if (!spec_src) {
+		g_warning ("no property with the name '%s' exists in object of class '%s'",
+		           prop_src, G_OBJECT_CLASS_NAME (cls));
+		return NULL;
+	}
+	
+	dests = NULL;
+	spec_dest = NULL;
+	va_start(va, prop_dest);
+	for (;;) {
+		dest = G_OBJECT (va_arg (va, GObject*));
+		if (!dest)
+			break;
+		
+		g_return_val_if_fail (G_IS_OBJECT (dest), NULL);
+		
+		cls = G_OBJECT_GET_CLASS (dest);
+		spec = g_object_class_find_property (cls, prop_dest);
+		if (!spec) {
+			g_warning ("no property with the name '%s' exists in object of class '%s'",
+			           prop_dest, G_OBJECT_CLASS_NAME (cls));
+			return NULL;
+		} 
+		
+		if (spec_dest && spec->value_type != spec_dest->value_type) {
+			g_warning ("destination property '%s' has a different type between objects in binding: %s != %s",
+			           prop_dest, g_type_name (spec_dest->value_type), g_type_name (spec->value_type));
+			return NULL;
+		} 
+		
+		dests = g_slist_prepend (dests, dest);
+		spec_dest = spec;
+	}
+
+	g_return_val_if_fail (spec_dest, NULL);
+	g_return_val_if_fail (dests, NULL);
+	
+	binding = g_new0 (Binding, 1);
+
+	binding->obj_src = obj_src;
+	g_object_weak_ref (obj_src, binding_src_gone, binding);
+	binding->prop_src = spec_src;
+	g_param_spec_ref (spec_src);
+	binding->transform = transform;
+	detail = g_strdup_printf ("notify::%s", prop_src);
+	binding->connection = g_signal_connect (obj_src, detail, G_CALLBACK (binding_fire), binding);
+	g_free (detail);
+	
+	binding->obj_dests = dests;
+	binding->prop_dest = spec_dest;
+	g_param_spec_ref (spec_dest);
+	for (l = binding->obj_dests; l; l = g_slist_next (l))
+		g_object_weak_ref (l->data, binding_dest_gone, binding);
+
+	binding->references = 1;
+	
+	/* Note this in all bindings */
+	if (!all_bindings)
+		all_bindings = g_hash_table_new (g_direct_hash, g_direct_equal);
+	g_hash_table_insert (all_bindings, binding, binding);
+	
+	/* Transfer the first time */
+	binding_fire (obj_src, spec_src, binding);
+	
+	return binding;
+}
+
+static GHashTable *all_transfers = NULL;
+
+typedef struct _Transfer {
+	GObject *object;
+	GObject *dest;
+	gulong connection;
+	SeahorseTransfer callback;
+} Transfer;
+
+static void transfer_free (Transfer *transfer);
+
+static void
+transfer_gone (gpointer data, GObject *was)
+{
+	Transfer *transfer = (Transfer*)data;
+	if (transfer->object == was)
+		transfer->object = NULL;
+	else if (transfer->dest == was)
+		transfer->dest = NULL;
+	else
+		g_assert_not_reached ();
+	transfer_free (transfer);
+}
+
+static void 
+transfer_free (Transfer *transfer)
+{
+	g_assert (transfer);
+	
+	if (G_IS_OBJECT (transfer->object)) {
+		g_object_weak_unref (transfer->object, transfer_gone, transfer);
+		g_signal_handler_disconnect (transfer->object, transfer->connection);
+		transfer->object = NULL;
+	}
+	
+	if (G_IS_OBJECT (transfer->dest)) {
+		g_object_weak_unref (transfer->dest, transfer_gone, transfer);
+		transfer->dest = NULL;
+	}
+	
+	g_free (transfer);
+	
+	/* Remove from the list of all notifies */
+	g_assert (all_transfers);
+	g_hash_table_remove (all_transfers, transfer);
+	if (g_hash_table_size (all_transfers) == 0) {
+		g_hash_table_destroy (all_transfers);
+		all_transfers = NULL;
+	}
+}
+
+static void
+transfer_fire (GObject *object, GParamSpec *spec, gpointer data)
+{
+	Transfer *transfer = (Transfer*)data;
+	g_assert (transfer->object == object);
+	(transfer->callback) (object, transfer->dest);
+}
+
+gpointer 
+seahorse_bind_objects (const gchar *property, gpointer object, 
+                       SeahorseTransfer callback, gpointer dest)
+{
+	GObjectClass *cls;
+	Transfer *transfer;
+	gchar *detail;
+
+	g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+	g_return_val_if_fail (G_IS_OBJECT (dest), NULL);
+	g_return_val_if_fail (callback, NULL);
+	
+	if (property) {
+		cls = G_OBJECT_GET_CLASS (object);
+		if (!g_object_class_find_property (cls, property)) {
+			g_warning ("no property with the name '%s' exists in object of class '%s'",
+			           property, G_OBJECT_CLASS_NAME (cls));
+			return NULL;
+		}
+	}
+	
+	transfer = g_new0 (Transfer, 1);
+
+	transfer->object = object;
+	g_object_weak_ref (object, transfer_gone, transfer);
+	transfer->dest = dest;
+	g_object_weak_ref (dest, transfer_gone, transfer);
+	transfer->callback = callback;
+	
+	if (property) {
+		detail = g_strdup_printf ("notify::%s", property);
+		transfer->connection = g_signal_connect (object, detail, G_CALLBACK (transfer_fire), transfer);
+		g_free (detail);
+	} else {
+		transfer->connection = g_signal_connect (object, "notify", G_CALLBACK (transfer_fire), transfer);
+	}
+	
+	/* Note this in all bindings */
+	if (!all_transfers)
+		all_transfers = g_hash_table_new (g_direct_hash, g_direct_equal);
+	g_hash_table_insert (all_transfers, transfer, transfer);
+	
+	/* Transfer the first time */
+	transfer_fire (object, NULL, transfer);
+	
+	return transfer;
+}
+
+void
+seahorse_bind_disconnect (gpointer what)
+{
+	g_return_if_fail (what);
+	if (all_bindings && g_hash_table_lookup (all_bindings, what))
+		binding_unref ((Binding*)what);
+	else if (all_transfers && g_hash_table_lookup (all_transfers, what))
+		transfer_free ((Transfer*)what);
+}
diff --git a/lib/seahorse-bind.h b/lib/seahorse-bind.h
new file mode 100644
index 0000000..166fe65
--- /dev/null
+++ b/lib/seahorse-bind.h
@@ -0,0 +1,43 @@
+/* 
+ * Seahorse
+ * 
+ * Copyright (C) 2008 Stefan Walter
+ * 
+ * This program 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 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
+ * Lesser General Public License for more details.
+ *  
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.  
+ */
+   
+#ifndef SEAHORSEBIND_H_
+#define SEAHORSEBIND_H_
+
+#include <glib-object.h>
+
+typedef gboolean (*SeahorseTransform) (const GValue *src, GValue *dest);
+
+gpointer seahorse_bind_property      (const gchar *prop_src, gpointer obj_src, 
+                                      const gchar *prop_dest, gpointer obj_dest);
+
+gpointer seahorse_bind_property_full (const gchar *prop_src, gpointer obj_src,
+                                      SeahorseTransform transform, 
+                                      const gchar *prop_dest, ...) G_GNUC_NULL_TERMINATED;
+
+typedef gboolean (*SeahorseTransfer) (GObject *src, GObject *dest);
+
+gpointer seahorse_bind_objects       (const gchar *property, gpointer obj_src,
+                                      SeahorseTransfer transfer, gpointer obj_dest);
+
+void     seahorse_bind_disconnect    (gpointer binding);
+
+#endif /* SEAHORSEBIND_H_ */



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