diff options
-rw-r--r-- | e-util/Makefile.am | 2 | ||||
-rw-r--r-- | e-util/e-binding.c | 536 | ||||
-rw-r--r-- | e-util/e-binding.h | 118 |
3 files changed, 656 insertions, 0 deletions
diff --git a/e-util/Makefile.am b/e-util/Makefile.am index 73665a3516..ee54891c1d 100644 --- a/e-util/Makefile.am +++ b/e-util/Makefile.am @@ -41,6 +41,7 @@ privsolib_LTLIBRARIES = libeutil.la libeconduit.la eutilinclude_HEADERS = \ e-bconf-map.h \ + e-binding.h \ e-categories-config.h \ e-config.h \ e-config-listener.h \ @@ -82,6 +83,7 @@ eutilinclude_HEADERS = \ libeutil_la_SOURCES = \ $(eutilinclude_HEADERS) \ e-bconf-map.c \ + e-binding.c \ e-categories-config.c \ e-config.c \ e-config-listener.c \ diff --git a/e-util/e-binding.c b/e-util/e-binding.c new file mode 100644 index 0000000000..6a29166cc1 --- /dev/null +++ b/e-util/e-binding.c @@ -0,0 +1,536 @@ +/* + * e-binding.c + * + * 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 of the License, or (at your option) version 3. + * + * 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-binding.h" + +static gboolean +e_binding_transform_negate (const GValue *src_value, + GValue *dst_value) +{ + if (!g_value_transform (src_value, dst_value)) + return FALSE; + + g_value_set_boolean (dst_value, !g_value_get_boolean (dst_value)); + + return TRUE; +} + +static void +e_bind_properties_transfer (GObject *src_object, + GParamSpec *src_pspec, + GObject *dst_object, + GParamSpec *dst_pspec, + EBindingTransform transform, + gpointer user_data) +{ + const gchar *src_name; + const gchar *dst_name; + gboolean result; + GValue src_value = { 0, }; + GValue dst_value = { 0, }; + + src_name = g_param_spec_get_name (src_pspec); + dst_name = g_param_spec_get_name (dst_pspec); + + g_value_init (&src_value, G_PARAM_SPEC_VALUE_TYPE (src_pspec)); + g_object_get_property (src_object, src_name, &src_value); + + g_value_init (&dst_value, G_PARAM_SPEC_VALUE_TYPE (dst_pspec)); + result = (*transform) (&src_value, &dst_value, user_data); + + g_value_unset (&src_value); + + g_return_if_fail (result); + + g_param_value_validate (dst_pspec, &dst_value); + g_object_set_property (dst_object, dst_name, &dst_value); + g_value_unset (&dst_value); +} + +static void +e_bind_properties_notify (GObject *src_object, + GParamSpec *src_pspec, + gpointer data) +{ + EBindingLink *link = data; + + /* Block the destination handler for mutual bindings, + * so we don't recurse here. */ + if (link->dst_handler != 0) + g_signal_handler_block (link->dst_object, link->dst_handler); + + e_bind_properties_transfer ( + src_object, src_pspec, + link->dst_object, link->dst_pspec, + link->transform, link->user_data); + + /* Unblock destination handler. */ + if (link->dst_handler != 0) + g_signal_handler_unblock (link->dst_object, link->dst_handler); +} + +static void +e_binding_on_dst_object_destroy (gpointer data, + GObject *object) +{ + EBinding *binding = data; + + binding->link.dst_object = NULL; + + /* Calls e_binding_on_disconnect() */ + g_signal_handler_disconnect ( + binding->src_object, binding->link.handler); +} + +static void +e_binding_on_disconnect (gpointer data, + GClosure *closure) +{ + EBindingLink *link = data; + EBinding *binding; + + binding = (EBinding *) + (((gchar *) link) - G_STRUCT_OFFSET (EBinding, link)); + + if (binding->base.destroy != NULL) + binding->base.destroy (link->user_data); + + if (link->dst_object != NULL) + g_object_weak_unref ( + link->dst_object, + e_binding_on_dst_object_destroy, binding); + + g_slice_free (EBinding, binding); +} + +/* Recursively calls e_mutual_binding_on_disconnect_object2() */ +static void +e_mutual_binding_on_disconnect_object1 (gpointer data, + GClosure *closure) +{ + EMutualBinding *binding; + EBindingLink *link = data; + GObject *object2; + + binding = (EMutualBinding *) + (((gchar *) link) - G_STRUCT_OFFSET (EMutualBinding, direct)); + binding->reverse.dst_object = NULL; + + object2 = binding->direct.dst_object; + if (object2 != NULL) { + if (binding->base.destroy != NULL) + binding->base.destroy (binding->direct.user_data); + binding->direct.dst_object = NULL; + g_signal_handler_disconnect (object2, binding->reverse.handler); + g_slice_free (EMutualBinding, binding); + } +} + +/* Recursively calls e_mutual_binding_on_disconnect_object1() */ +static void +e_mutual_binding_on_disconnect_object2 (gpointer data, + GClosure *closure) +{ + EMutualBinding *binding; + EBindingLink *link = data; + GObject *object1; + + binding = (EMutualBinding *) + (((gchar *) link) - G_STRUCT_OFFSET (EMutualBinding, reverse)); + binding->direct.dst_object = NULL; + + object1 = binding->reverse.dst_object; + if (object1 != NULL) { + binding->reverse.dst_object = NULL; + g_signal_handler_disconnect (object1, binding->direct.handler); + } +} + +static void +e_binding_link_init (EBindingLink *link, + GObject *src_object, + const gchar *src_property, + GObject *dst_object, + GParamSpec *dst_pspec, + EBindingTransform transform, + GClosureNotify destroy_notify, + gpointer user_data) +{ + gchar *signal_name; + + link->dst_object = dst_object; + link->dst_pspec = dst_pspec; + link->dst_handler = 0; + link->transform = transform; + link->user_data = user_data; + + signal_name = g_strconcat ("notify::", src_property, NULL); + link->handler = g_signal_connect_data ( + src_object, signal_name, + G_CALLBACK (e_bind_properties_notify), + link, destroy_notify, 0); + g_free (signal_name); +} + +/** + * e_binding_new: + * @src_object: The source #GObject. + * @src_property: The name of the property to bind from. + * @dst_object: The destination #GObject. + * @dst_property: The name of the property to bind to. + * + * One-way binds @src_property in @src_object to @dst_property + * in @dst_object. + * + * Before binding the value of @dst_property is set to the + * value of @src_property. + * + * Returns: The descriptor of the binding. It is automatically + * removed if one of the objects is finalized. + **/ +EBinding * +e_binding_new (GObject *src_object, + const gchar *src_property, + GObject *dst_object, + const gchar *dst_property) +{ + return e_binding_new_full ( + src_object, src_property, + dst_object, dst_property, + NULL, NULL, NULL); +} + +/** + * e_binding_new_full: + * @src_object: The source #GObject. + * @src_property: The name of the property to bind from. + * @dst_object: The destination #GObject. + * @dst_property: The name of the property to bind to. + * @transform: Transformation function or %NULL. + * @destroy_notify: Callback function that is called on + * disconnection with @user_data or %NULL. + * @user_data: User data associated with the binding. + * + * One-way binds @src_property in @src_object to @dst_property + * in @dst_object. + * + * Before binding the value of @dst_property is set to the + * value of @src_property. + * + * Returns: The descriptor of the binding. It is automatically + * removed if one of the objects is finalized. + **/ +EBinding * +e_binding_new_full (GObject *src_object, + const gchar *src_property, + GObject *dst_object, + const gchar *dst_property, + EBindingTransform transform, + GDestroyNotify destroy_notify, + gpointer user_data) +{ + EBinding *binding; + GParamSpec *src_pspec; + GParamSpec *dst_pspec; + + g_return_val_if_fail (G_IS_OBJECT (src_object), NULL); + g_return_val_if_fail (G_IS_OBJECT (dst_object), NULL); + + src_pspec = g_object_class_find_property ( + G_OBJECT_GET_CLASS (src_object), src_property); + dst_pspec = g_object_class_find_property ( + G_OBJECT_GET_CLASS (dst_object), dst_property); + + if (transform == NULL) + transform = (EBindingTransform) g_value_transform; + + e_bind_properties_transfer ( + src_object, src_pspec, + dst_object, dst_pspec, + transform, user_data); + + binding = g_slice_new (EBinding); + binding->src_object = src_object; + binding->base.destroy = destroy_notify; + + e_binding_link_init ( + &binding->link, src_object, src_property, dst_object, + dst_pspec, transform, e_binding_on_disconnect, user_data); + + g_object_weak_ref ( + dst_object, e_binding_on_dst_object_destroy, binding); + + return binding; +} + +/** + * e_binding_new_with_negation: + * @src_object: The source #GObject. + * @src_property: The name of the property to bind from. + * @dst_object: The destination #GObject. + * @dst_property: The name of the property to bind to. + * + * Convenience function for binding with boolean negation of value. + * + * Return: The descriptor of the binding. It is automatically + * removed if one of the objects is finalized. + **/ +EBinding * +e_binding_new_with_negation (GObject *src_object, + const gchar *src_property, + GObject *dst_object, + const gchar *dst_property) +{ + EBindingTransform transform; + + transform = (EBindingTransform) e_binding_transform_negate; + + return e_binding_new_full ( + src_object, src_property, + dst_object, dst_property, + transform, NULL, NULL); +} + +/** + * e_binding_unbind: + * @binding: An #EBinding to unbind. + * + * Disconnects the binding between two properties. Should be + * rarely used by applications. + * + * This functions also calls the @destroy_notify function that + * was specified when @binding was created. + **/ +void +e_binding_unbind (EBinding *binding) +{ + g_signal_handler_disconnect ( + binding->src_object, binding->link.handler); +} + +/** + * e_mutual_binding_new: + * @object1 : The first #GObject. + * @property1: The first property to bind. + * @object2 : The second #GObject. + * @property2: The second property to bind. + * + * Mutually binds values of two properties. + * + * Before binding the value of @property2 is set to the value + * of @property1. + * + * Returns: The descriptor of the binding. It is automatically + * removed if one of the objects is finalized. + **/ +EMutualBinding * +e_mutual_binding_new (GObject *object1, + const gchar *property1, + GObject *object2, + const gchar *property2) +{ + return e_mutual_binding_new_full ( + object1, property1, + object2, property2, + NULL, NULL, NULL, NULL); +} + +/** + * e_mutual_binding_new_full: + * @object1: The first #GObject. + * @property1: The first property to bind. + * @object2: The second #GObject. + * @property2: The second property to bind. + * @transform: Transformation function or %NULL. + * @reverse_transform: The inverse transformation function or %NULL. + * @destroy_notify: Callback function called on disconnection with + * @user_data as argument or %NULL. + * @user_data: User data associated with the binding. + * + * Mutually binds values of two properties. + * + * Before binding the value of @property2 is set to the value of + * @property1. + * + * Both @transform and @reverse_transform should simultaneously be + * %NULL or non-%NULL. If they are non-%NULL, they should be reverse + * in each other. + * + * Returns: The descriptor of the binding. It is automatically + * removed if one of the objects is finalized. + **/ +EMutualBinding * +e_mutual_binding_new_full (GObject *object1, + const gchar *property1, + GObject *object2, + const gchar *property2, + EBindingTransform transform, + EBindingTransform reverse_transform, + GDestroyNotify destroy_notify, + gpointer user_data) +{ + EMutualBinding *binding; + GParamSpec *pspec1; + GParamSpec *pspec2; + + g_return_val_if_fail (G_IS_OBJECT (object1), NULL); + g_return_val_if_fail (G_IS_OBJECT (object2), NULL); + + pspec1 = g_object_class_find_property ( + G_OBJECT_GET_CLASS (object1), property1); + pspec2 = g_object_class_find_property ( + G_OBJECT_GET_CLASS (object2), property2); + + if (transform == NULL) + transform = (EBindingTransform) g_value_transform; + + if (reverse_transform == NULL) + reverse_transform = (EBindingTransform) g_value_transform; + + e_bind_properties_transfer ( + object1, pspec1, object2, + pspec2, transform, user_data); + + binding = g_slice_new (EMutualBinding); + binding->base.destroy = destroy_notify; + + e_binding_link_init ( + &binding->direct, + object1, property1, object2, pspec2, transform, + e_mutual_binding_on_disconnect_object1, user_data); + + e_binding_link_init ( + &binding->reverse, + object2, property2, object1, pspec1, reverse_transform, + e_mutual_binding_on_disconnect_object2, user_data); + + /* Tell each link about the reverse link for mutual bindings, + * to make sure that we do not ever recurse in notify (yeah, + * the GObject notify dispatching is really weird!). */ + binding->direct.dst_handler = binding->reverse.handler; + binding->reverse.dst_handler = binding->direct.handler; + + return binding; +} + +/** + * e_mutual_binding_new_with_negation: + * @object1: The first #GObject. + * @property1: The first property to bind. + * @object2: The second #GObject. + * @property2: The second property to bind. + * + * Convenience function for binding with boolean negation of value. + * + * Returns: The descriptor of the binding. It is automatically removed + * if one of the objects if finalized. + **/ +EMutualBinding* +e_mutual_binding_new_with_negation (GObject *object1, + const gchar *property1, + GObject *object2, + const gchar *property2) +{ + EBindingTransform transform; + + transform = (EBindingTransform) e_binding_transform_negate; + + return e_mutual_binding_new_full ( + object1, property1, + object2, property2, + transform, transform, + NULL, NULL); +} + +/** + * e_mutual_binding_unbind: + * @binding: An #EMutualBinding to unbind. + * + * Disconnects the binding between two properties. Should be + * rarely used by applications. + * + * This functions also calls the @destroy_notify function that + * was specified when @binding was created. + **/ +void +e_mutual_binding_unbind (EMutualBinding *binding) +{ + g_signal_handler_disconnect ( + binding->reverse.dst_object, binding->direct.handler); +} + +/** + * e_binding_transform_color_to_string: + * @src_value: a #GValue of type #GDK_TYPE_COLOR + * @dst_value: a #GValue of type #G_TYPE_STRING + * @user_data: not used + * + * Transforms a #GdkColor value to a color string specification. + * + * Returns: %TRUE always + **/ +gboolean +e_binding_transform_color_to_string (const GValue *src_value, + GValue *dst_value, + gpointer user_data) +{ + const GdkColor *color; + gchar *string; + + color = g_value_get_boxed (src_value); + string = gdk_color_to_string (color); + g_value_set_string (dst_value, string); + g_free (string); + + return TRUE; +} + +/** + * e_binding_transform_string_to_color: + * @src_value: a #GValue of type #G_TYPE_STRING + * @dst_value: a #GValue of type #GDK_TYPE_COLOR + * @user_data: not used + * + * Transforms a color string specification to a #GdkColor. + * + * Returns: %TRUE if color string specification was valid + **/ +gboolean +e_binding_transform_string_to_color (const GValue *src_value, + GValue *dst_value, + gpointer user_data) +{ + GdkColor color; + const gchar *string; + gboolean success = FALSE; + + string = g_value_get_string (src_value); + if (gdk_color_parse (string, &color)) { + g_value_set_boxed (dst_value, &color); + success = TRUE; + } + + return success; +} diff --git a/e-util/e-binding.h b/e-util/e-binding.h new file mode 100644 index 0000000000..676093bfad --- /dev/null +++ b/e-util/e-binding.h @@ -0,0 +1,118 @@ +/* + * e-binding.h + * + * 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 of the License, or (at your option) version 3. + * + * 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +/* This is a direct rip-off of Xfce's excellent ExoBinding API, + * which binds two GObject properties together. ExoBinding was + * written by Benedikt Meurer <benny@xfce.org>. */ + +#ifndef E_BINDING_H +#define E_BINDING_H + +#include <gdk/gdk.h> + +G_BEGIN_DECLS + +typedef struct _EBinding EBinding; +typedef struct _EBindingBase EBindingBase; +typedef struct _EBindingLink EBindingLink; +typedef struct _EMutualBinding EMutualBinding; + +typedef gboolean (*EBindingTransform) (const GValue *src_value, + GValue *dst_value, + gpointer user_data); + +struct _EBindingBase { + GDestroyNotify destroy; +}; + +struct _EBindingLink { + GObject *dst_object; + GParamSpec *dst_pspec; + gulong dst_handler; /* only set for mutual bindings */ + gulong handler; + EBindingTransform transform; + gpointer user_data; +}; + +struct _EBinding { + /*< private >*/ + GObject *src_object; + EBindingBase base; + EBindingLink link; +}; + +struct _EMutualBinding { + /*< private >*/ + EBindingBase base; + EBindingLink direct; + EBindingLink reverse; +}; + +EBinding * e_binding_new (GObject *src_object, + const gchar *src_property, + GObject *dst_object, + const gchar *dst_property); +EBinding * e_binding_new_full (GObject *src_object, + const gchar *src_property, + GObject *dst_object, + const gchar *dst_property, + EBindingTransform transform, + GDestroyNotify destroy_notify, + gpointer user_data); +EBinding * e_binding_new_with_negation (GObject *src_object, + const gchar *src_property, + GObject *dst_object, + const gchar *dst_property); +void e_binding_unbind (EBinding *binding); + +EMutualBinding *e_mutual_binding_new (GObject *object1, + const gchar *property1, + GObject *object2, + const gchar *property2); +EMutualBinding *e_mutual_binding_new_full (GObject *object1, + const gchar *property1, + GObject *object2, + const gchar *property2, + EBindingTransform transform, + EBindingTransform reverse_transform, + GDestroyNotify destroy_notify, + gpointer user_data); +EMutualBinding *e_mutual_binding_new_with_negation + (GObject *object1, + const gchar *property1, + GObject *object2, + const gchar *property2); +void e_mutual_binding_unbind (EMutualBinding *binding); + + +/* Useful transformation functions */ +gboolean e_binding_transform_color_to_string + (const GValue *src_value, + GValue *dst_value, + gpointer user_data); +gboolean e_binding_transform_string_to_color + (const GValue *src_value, + GValue *dst_value, + gpointer user_data); + +G_END_DECLS + +#endif /* E_BINDING_H */ |