diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2009-04-24 04:50:54 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2009-04-24 04:50:54 +0800 |
commit | 0006bfb58ac91bac0768590827bd3dabba72c638 (patch) | |
tree | cfac8f30da40622fe1cbc56ce5518911e38e0028 | |
parent | 6350c087b31b10d8d05b774e1829b3ff80812f90 (diff) | |
download | gsoc2013-evolution-0006bfb58ac91bac0768590827bd3dabba72c638.tar.gz gsoc2013-evolution-0006bfb58ac91bac0768590827bd3dabba72c638.tar.zst gsoc2013-evolution-0006bfb58ac91bac0768590827bd3dabba72c638.zip |
Bug 577898 – Port ExoBinding to Evolution
Port Xfce's ExoBinding API to Evolution as EBinding. What this does is
allow you to bind two GObject properties together such that their values
are automatically kept in sync. The API also supports transformation
functions, such as boolean inversion.
I use this API heavily on the kill-bonobo branch and also in the
attachment UI rewrite, so I'm merging this feature early.
-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 */ |