Also make it more fault-tolerant by warning about non-existent property names instead of just crashing.
558 lines
16 KiB
C
558 lines
16 KiB
C
/*
|
|
* 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 gpointer
|
|
e_binding_warn (GObject *object,
|
|
const gchar *property_name)
|
|
{
|
|
g_warning (
|
|
"%s instances have no `%s' property to bind to",
|
|
G_OBJECT_TYPE_NAME (object), property_name);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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 (gpointer src_object,
|
|
const gchar *src_property,
|
|
gpointer 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 (gpointer src_object,
|
|
const gchar *src_property,
|
|
gpointer 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 (src_pspec == NULL)
|
|
return e_binding_warn (src_object, src_property);
|
|
if (dst_pspec == NULL)
|
|
return e_binding_warn (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 (gpointer src_object,
|
|
const gchar *src_property,
|
|
gpointer 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 (gpointer object1,
|
|
const gchar *property1,
|
|
gpointer 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 (gpointer object1,
|
|
const gchar *property1,
|
|
gpointer 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 (pspec1 == NULL)
|
|
return e_binding_warn (object1, property1);
|
|
if (pspec2 == NULL)
|
|
return e_binding_warn (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 (gpointer object1,
|
|
const gchar *property1,
|
|
gpointer 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;
|
|
}
|