aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/gconf-bridge.c
blob: 3de48b6e01ecd50807d37362dd348dcfeb74a1a2 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  















                                                                    

                                                                   

   
                    
                   
      
 
                    
                           
                   
                   




                            
 












                                                             
 

                              
                   


















                                                                          
                          


                                  
                                     







                              
                   


























                                                                          





                                                                         

                                                
 
                        











                                                        

                              
 
                                                      
 
                                        
 


                                                                           
 
                                  
 
                      





                          
                                                                          






                                             
                                                    
 
                              





                                      
                                    
 
                     
 
                          








                                                     
                                                       
 
                                
 
                                                                     
                                                                          
 
                                                

                                                 





















                                                                         


                                                                             

                       

                                                                           


                                                               
 

                                                              

                                                                               

                                                                  
 

                                  
 





                                                                        
 
     

                                   
 
                                                                            





                                                     




































































                                                                     
                       
                                                                      

                                                                     

                                                                             
     

                                                                    
      











                                                                        

                                                                     
                                                               
 

                                  
 

                      

                          
                                                                           
 

                                                                        

                                                                    

     
                               




                                                                       


                                              
 


                                
 


                                                                      
 
                                            

                                                                               




                                                                     
 

                                                          
 

                       
 
                                                              

 
                                                               



                                                          
                                                 
 
                                     
 
                                         
 
                     






                                                                        

                                                  

                                                
                                    
                                          
                                                    


                                                                       









                                                                                
                                   

                                                         






                                                             
                             
 

                                                                    
                                                 
 

                                                             
























                                                                            



                                                       
 



                             
 



                                                       

                                                                         


                                                                    



                                                                                

                         

                                                             
                                         
 







                                             
 
                             



                                                                   


                                                      




                                                                         

                                                     




                                                              

                                                             

                                                                   

                            

                                                                      

                  
                           

 
           


                                            
 

                                           
 



                                                                  


           


                                              
 

                                           
 



                                                                  


    
                                                          
                                                       
 

                                          
 

                                          
                                                       


    
                                                            
                                                         
 

                                          
 

                                          
                                                         

 



                                          
                                                                    
                                                 
                                                           
 

                                                             

                                                              
 


                                                            
 

                                                              
 


                                                                     

                                                     


                                                                      
 


                                                                             









                                                                        




                                     
 

                                                                              
 
                                                         
                                                                                    



                                                                                

                                                                                

                                                                                

                                                                                 

                                                                                 

                                                                                    



                                                                                 
 


                                
 
                                                                  

                                                                    

                                                                    

                                                                    


                                                                    
 
                                     
 
                     

 

                                                                      


                                                      
                                                     
                                                            
                                                          
 
                                                           

                                                           
 
                             


                                                                                    
 
                     



                                                   
                                                 
                                                          
                                                      
 

                                                           
 



                                                                                
                                                                                 

                                   



                                                                     



                                                                  







                                                               


                 
                                                        
 
                     



                                              
                                           


                                                

                                                           
 

                                                               
                                  
 
                                                        
 
                     






                                                               
                               
 

                                                                    
                                                 
 

                                                           
 

                                                             








                                                  
  











                                                                            



                                                  
 
                               
 


                                                         

                                 
                                           
 






                                                    

                                                             


                                                                   

                                                               

                                                                         

                                                                

                                                                          

                                                                   

















                                                                             




                                                                           



                                                                              







                                                                  





                                                                        








                                                             
 
                                                           

                                                                     

                                                           















                                                                      

                                                         

                                         
                                                    


                                                                            
 

                                         
                                                       




                                                                        
                                          

                                                                       

                                                     

                                                                     

                            

                                                                      

                  
                           





                                              

                                                           
 
                                     

                                                     










                                                                               








                                                                 
                                                         
 

                         

                                                                     
























                                                                       





                                                                 




                                 
 
                                                          

                        




                                                                
 

                                                      
 

                                                                 
 
                                                        
 

                                                                   

                          


                                                                    

                 
                                                                           


                                                                        

                                                                    
 
                                  
 
                                             
 
                     




                                                     


                                                    
 


                                  
 


                                                                      
 
                                                 


                                                        




                                                                     
 

                                                          
 

                       
 
                                                                     






                                                                  
                                  
 

                                                                       
                                                     
 

                                                             





                                                               

                                                   
 



                                                                             






                                      
  







                                                                           

                                                         

                                                              



                                                   
 


                                                                 

                                          




                                                                           



                                                                              

                         

                                 
                                              
 





                                           

                             



                                                                         

                                                       
                                  
                                                                     



                                                                                
                                                                    



                                                                                
                                                                    



                                                                                
                                                                       


                                                                                

                                                     




                                                                     

                                                             

                                                                        

                            

                                                                      

                  
                           






                                                     

                                                        

                                                                            

                                                                
 
                              
 

                                                              
 


                                                                     

                                                    













                                                                         










                                                 










                                                                         
                                                                         

                      
 
                         










                                                   
                                      
 

                                          


                                                               

                                                            








                                            
                             
 
                                             


                                                         


                               

                                                                



                                                                
                                                   

                                                       
 

                                                        


                                                                          
                                                  
 
                                         
 

                                    










                                                                     
                                                                      
 
/*
 * (C) 2005 OpenedHand Ltd.
 *
 * Author: Jorn Baayen <jorn@openedhand.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gtk/gtk.h>
#include <glib/gi18n-lib.h>
#include <string.h>
#include <stdlib.h>

#include "gconf-bridge.h"

struct _GConfBridge {
        GConfClient *client;

        GHashTable *bindings;
};

/* The data structures for the different kinds of bindings */
typedef enum {
        BINDING_PROP,
        BINDING_WINDOW,
        BINDING_LIST_STORE
} BindingType;

typedef struct {
        BindingType type;
        guint id;

        gboolean delayed_mode;

        gchar *key;
        guint val_notify_id;
        GSList *val_changes; /* List of changes made to GConf value,
                                that have not received change notification
                                yet. */

        GObject *object;
        GParamSpec *prop;
        gulong prop_notify_id;

        guint sync_timeout_id; /* Used in delayed mode */
} PropBinding;

typedef struct {
        BindingType type;
        guint id;

        gboolean bind_size;
        gboolean bind_pos;

        gchar *key_prefix;

        GtkWindow *window;
        gulong configure_event_id;
        gulong window_state_event_id;
        gulong unmap_id;
        guint sync_timeout_id;
} WindowBinding;

typedef struct {
        BindingType type;
        guint id;

        gchar *key;
        guint val_notify_id;
        GSList *val_changes; /* List of changes made to GConf value,
                                that have not received change notification
                                yet. */

        GtkListStore *list_store;
        guint row_inserted_id;
        guint row_changed_id;
        guint row_deleted_id;
        guint rows_reordered_id;

        guint sync_idle_id;
} ListStoreBinding;

/* Some trickery to be able to treat the data structures generically */
typedef union {
        BindingType      type;

        PropBinding      prop_binding;
        WindowBinding    window_binding;
        ListStoreBinding list_store_binding;
} Binding;

/* Function prototypes */
static void
unbind (Binding *binding);

static GConfBridge *bridge = NULL; /* Global GConfBridge object */

/* Free up all resources allocated by the GConfBridge. Called on exit. */
static void
destroy_bridge (void)
{
    g_hash_table_destroy (bridge->bindings);
    g_object_unref (bridge->client);

    g_free (bridge);
}

/**
 * gconf_bridge_get
 *
 * Returns the #GConfBridge. This is a singleton object.
 *
 * Return value: The #GConfBridge.
 **/
GConfBridge *
gconf_bridge_get (void)
{
    if (bridge)
        return bridge;

    gconf_bridge_install_default_error_handler ();

    bridge = g_new (GConfBridge, 1);

    bridge->client = gconf_client_get_default ();
    bridge->bindings = g_hash_table_new_full (NULL, NULL, NULL,
                          (GDestroyNotify) unbind);

    g_atexit (destroy_bridge);

    return bridge;
}

/**
 * gconf_bridge_get_client
 * @bridge: A #GConfBridge
 *
 * Returns the #GConfClient used by @bridge. This is the same #GConfClient
 * as returned by gconf_client_get_default().
 *
 * Return value: A #GConfClient.
 **/
GConfClient *
gconf_bridge_get_client (GConfBridge *bridge)
{
    g_return_val_if_fail (bridge != NULL, NULL);

    return bridge->client;
}

/* Generate an ID for a new binding */
static guint
new_id (void)
{
    static guint id_counter = 0;

    id_counter++;

    return id_counter;
}

/*
 * Property bindings
 */

/* Syncs a value from GConf to an object property */
static void
prop_binding_sync_pref_to_prop (PropBinding *binding,
                                GConfValue *pref_value)
{
    GValue src_value, value;

        /* Make sure we don't enter an infinite synchronizing loop */
    g_signal_handler_block (binding->object, binding->prop_notify_id);

    memset (&src_value, 0, sizeof (GValue));

        /* First, convert GConfValue to GValue */
    switch (pref_value->type) {
    case GCONF_VALUE_STRING:
        g_value_init (&src_value, G_TYPE_STRING);
        g_value_set_string (&src_value,
                    gconf_value_get_string (pref_value));
        break;
    case GCONF_VALUE_INT:
        g_value_init (&src_value, G_TYPE_INT);
        g_value_set_int (&src_value,
                 gconf_value_get_int (pref_value));
        break;
    case GCONF_VALUE_BOOL:
        g_value_init (&src_value, G_TYPE_BOOLEAN);
        g_value_set_boolean (&src_value,
                     gconf_value_get_bool (pref_value));
        break;
    case GCONF_VALUE_FLOAT:
        g_value_init (&src_value, G_TYPE_FLOAT);
        g_value_set_float (&src_value,
                   gconf_value_get_float (pref_value));
        break;
    default:
                g_warning ("prop_binding_sync_pref_to_prop: Unhandled value "
                           "type '%d'.\n", pref_value->type);

        return;
    }

        /* Then convert to the type expected by the object, if necessary */
    memset (&value, 0, sizeof (GValue));
    g_value_init (&value,
              G_PARAM_SPEC_VALUE_TYPE (binding->prop));

    if (src_value.g_type != value.g_type) {
        if (!g_value_transform (&src_value, &value)) {
                        g_warning ("prop_binding_sync_pref_to_prop: Failed to "
                                   "transform a \"%s\" to a \"%s\".",
                   g_type_name (src_value.g_type),
                   g_type_name (value.g_type));

            goto done;
        }

        g_object_set_property (binding->object,
                       binding->prop->name, &value);
    } else {
        g_object_set_property (binding->object,
                       binding->prop->name, &src_value);
    }

done:
    g_value_unset (&src_value);
    g_value_unset (&value);

    g_signal_handler_unblock (binding->object, binding->prop_notify_id);
}

/* Syncs an object property to GConf */
static void
prop_binding_sync_prop_to_pref (PropBinding *binding)
{
    GValue value;
    GConfValue *gconf_value;

    memset (&value, 0, sizeof (GValue));

    g_value_init (&value,
              G_PARAM_SPEC_VALUE_TYPE (binding->prop));
    g_object_get_property (binding->object,
                   binding->prop->name,
                   &value);

    switch (value.g_type) {
    case G_TYPE_STRING:
        gconf_value = gconf_value_new (GCONF_VALUE_STRING);
        gconf_value_set_string (gconf_value,
                    g_value_get_string (&value));
        break;
    case G_TYPE_INT:
        gconf_value = gconf_value_new (GCONF_VALUE_INT);
        gconf_value_set_int (gconf_value,
                     g_value_get_int (&value));
        break;
    case G_TYPE_UINT:
        gconf_value = gconf_value_new (GCONF_VALUE_INT);
        gconf_value_set_int (gconf_value,
                     g_value_get_uint (&value));
        break;
    case G_TYPE_LONG:
        gconf_value = gconf_value_new (GCONF_VALUE_INT);
        gconf_value_set_int (gconf_value,
                     g_value_get_long (&value));
        break;
    case G_TYPE_ULONG:
        gconf_value = gconf_value_new (GCONF_VALUE_INT);
        gconf_value_set_int (gconf_value,
                     g_value_get_ulong (&value));
        break;
    case G_TYPE_INT64:
        gconf_value = gconf_value_new (GCONF_VALUE_INT);
        gconf_value_set_int (gconf_value,
                     g_value_get_int64 (&value));
        break;
    case G_TYPE_UINT64:
        gconf_value = gconf_value_new (GCONF_VALUE_INT);
        gconf_value_set_int (gconf_value,
                     g_value_get_uint64 (&value));
        break;
    case G_TYPE_CHAR:
        gconf_value = gconf_value_new (GCONF_VALUE_INT);
        gconf_value_set_int (gconf_value,
                     g_value_get_char (&value));
        break;
    case G_TYPE_UCHAR:
        gconf_value = gconf_value_new (GCONF_VALUE_INT);
        gconf_value_set_int (gconf_value,
                     g_value_get_uchar (&value));
        break;
    case G_TYPE_ENUM:
        gconf_value = gconf_value_new (GCONF_VALUE_INT);
        gconf_value_set_int (gconf_value,
                     g_value_get_enum (&value));
        break;
    case G_TYPE_BOOLEAN:
        gconf_value = gconf_value_new (GCONF_VALUE_BOOL);
        gconf_value_set_bool (gconf_value,
                      g_value_get_boolean (&value));
        break;
    case G_TYPE_DOUBLE:
        gconf_value = gconf_value_new (GCONF_VALUE_FLOAT);
#ifdef HAVE_CORBA_GCONF
                /* FIXME we cast to a gfloat explicitly as CORBA GConf
                 * uses doubles in its API, but treats them as floats
                 * when transporting them over CORBA. See #322837 */
        gconf_value_set_float (gconf_value,
                       (gfloat) g_value_get_double (&value));
#else
        gconf_value_set_float (gconf_value,
                       g_value_get_double (&value));
#endif
        break;
    case G_TYPE_FLOAT:
        gconf_value = gconf_value_new (GCONF_VALUE_FLOAT);
        gconf_value_set_float (gconf_value,
                       g_value_get_float (&value));
        break;
    default:
        if (g_type_is_a (value.g_type, G_TYPE_ENUM)) {
            gconf_value = gconf_value_new (GCONF_VALUE_INT);
            gconf_value_set_int (gconf_value,
                         g_value_get_enum (&value));
        } else {
                        g_warning ("prop_binding_sync_prop_to_pref: "
                                   "Unhandled value type '%s'.\n",
                   g_type_name (value.g_type));

            goto done;
        }

        break;
    }

        /* Set to GConf */
    gconf_client_set (bridge->client, binding->key, gconf_value, NULL);

        /* Store until change notification comes in, so that we are able
         * to ignore it */
    binding->val_changes = g_slist_append (binding->val_changes,
                           gconf_value);

done:
    g_value_unset (&value);
}

/* Called when a GConf value bound to an object property has changed */
static void
prop_binding_pref_changed (GConfClient *client,
                           guint cnxn_id,
                           GConfEntry *entry,
                           gpointer user_data)
{
    GConfValue *gconf_value;
    PropBinding *binding;
    GSList *l;

    gconf_value = gconf_entry_get_value (entry);
    if (!gconf_value)
        return; /* NULL means that the value has been unset */

    binding = (PropBinding *) user_data;

        /* Check that this notification is not caused by sync_prop_to_pref() */
    l = g_slist_find_custom (binding->val_changes,
                 gconf_value,
                 (GCompareFunc) gconf_value_compare);
    if (l) {
        gconf_value_free (l->data);

        binding->val_changes = g_slist_delete_link
            (binding->val_changes, l);

        return;
    }

    prop_binding_sync_pref_to_prop (binding, gconf_value);
}

/* Performs a scheduled prop-to-pref sync for a prop binding in
 * delay mode */
static gboolean
prop_binding_perform_scheduled_sync (PropBinding *binding)
{
    prop_binding_sync_prop_to_pref (binding);

    binding->sync_timeout_id = 0;

    g_object_unref (binding->object);

    return FALSE;
}

#define PROP_BINDING_SYNC_DELAY 100 /* Delay for bindings with "delayed"
                                       set to TRUE, in ms */

/* Called when an object property has changed */
static void
prop_binding_prop_changed (GObject *object,
                           GParamSpec *param_spec,
                           PropBinding *binding)
{
    if (binding->delayed_mode) {
                /* Just schedule a sync */
        if (binding->sync_timeout_id == 0) {
                        /* We keep a reference on the object as long as
                         * we haven't synced yet to make sure we don't
                         * lose any data */
            g_object_ref (binding->object);

            binding->sync_timeout_id =
                g_timeout_add
                    (PROP_BINDING_SYNC_DELAY,
                     (GSourceFunc)
                        prop_binding_perform_scheduled_sync,
                     binding);
        }
    } else {
                /* Directly sync */
        prop_binding_sync_prop_to_pref (binding);
    }
}

/* Called when an object is destroyed */
static void
prop_binding_object_destroyed (gpointer user_data,
                               GObject *where_the_object_was)
{
    PropBinding *binding;

    binding = (PropBinding *) user_data;
    binding->object = NULL; /* Don't do anything with the object
                                 * at unbind() */

    g_hash_table_remove (bridge->bindings,
                 GUINT_TO_POINTER (binding->id));
}

/**
 * gconf_bridge_bind_property_full
 * @bridge: A #GConfBridge
 * @key: A GConf key to be bound
 * @object: A #GObject
 * @prop: The property of @object to be bound
 * @delayed_sync: TRUE if there should be a delay between property changes
 * and syncs to GConf. Set to TRUE when binding to a rapidly-changing
 * property, for example the "value" property on a #GtkAdjustment.
 *
 * Binds @key to @prop, causing them to have the same value at all times.
 *
 * The types of @key and @prop should be compatible. Floats and doubles, and
 * ints, uints, longs, unlongs, int64s, uint64s, chars, uchars and enums
 * can be matched up. Booleans and strings can only be matched to their
 * respective types.
 *
 * On calling this function the current value of @key will be set to @prop.
 *
 * Return value: The ID of the new binding.
 **/
guint
gconf_bridge_bind_property_full (GConfBridge *bridge,
                                 const gchar *key,
                                 GObject *object,
                                 const gchar *prop,
                                 gboolean delayed_sync)
{
    GParamSpec *pspec;
    PropBinding *binding;
    gchar *signal;
    GConfValue *val;

    g_return_val_if_fail (bridge != NULL, 0);
    g_return_val_if_fail (key != NULL, 0);
    g_return_val_if_fail (G_IS_OBJECT (object), 0);
    g_return_val_if_fail (prop != NULL, 0);

        /* First, try to fetch the propertys GParamSpec off the object */
    pspec = g_object_class_find_property
                (G_OBJECT_GET_CLASS (object), prop);
    if (G_UNLIKELY (pspec == NULL)) {
                g_warning ("gconf_bridge_bind_property_full: A property \"%s\" "
                           "was not found. Please make sure you are passing "
                           "the right property name.", prop);

        return 0;
    }

        /* GParamSpec found: All good, create new binding. */
    binding = g_new (PropBinding, 1);

    binding->type = BINDING_PROP;
    binding->id = new_id ();
    binding->delayed_mode = delayed_sync;
    binding->val_changes = NULL;
    binding->key = g_strdup (key);
    binding->object = object;
    binding->prop = pspec;
    binding->sync_timeout_id = 0;

        /* Watch GConf key */
    binding->val_notify_id =
        gconf_client_notify_add (bridge->client, key,
                     prop_binding_pref_changed,
                     binding, NULL, NULL);

        /* Connect to property change notifications */
        signal = g_strconcat ("notify::", prop, NULL);
    binding->prop_notify_id =
        g_signal_connect (object, signal,
                  G_CALLBACK (prop_binding_prop_changed),
                  binding);
    g_free (signal);

        /* Sync object to value from GConf, if set */
    val = gconf_client_get (bridge->client, key, NULL);
    if (val) {
        prop_binding_sync_pref_to_prop (binding, val);
        gconf_value_free (val);
    }

        /* Handle case where watched object gets destroyed */
    g_object_weak_ref (object,
               prop_binding_object_destroyed, binding);

        /* Insert binding */
    g_hash_table_insert (bridge->bindings,
                 GUINT_TO_POINTER (binding->id), binding);

        /* Done */
    return binding->id;
}

static void
prop_binding_block_cb (gpointer hkey,
                       PropBinding *binding,
                       const gchar *key)
{
    g_return_if_fail (binding != NULL);
    g_return_if_fail (key != NULL);

    if (binding->type == BINDING_PROP && binding->key &&
        g_ascii_strcasecmp (binding->key, key) == 0)
        g_signal_handler_block (
            binding->object, binding->prop_notify_id);
}

static void
prop_binding_unblock_cb (gpointer hkey,
                         PropBinding *binding,
                         const gchar *key)
{
    g_return_if_fail (binding != NULL);
    g_return_if_fail (key != NULL);

    if (binding->type == BINDING_PROP && binding->key &&
        g_ascii_strcasecmp (binding->key, key) == 0)
        g_signal_handler_unblock (
            binding->object, binding->prop_notify_id);
}

void
gconf_bridge_block_property_bindings (GConfBridge *bridge,
                                      const gchar *key)
{
    g_return_if_fail (bridge != NULL);
    g_return_if_fail (key != NULL);

    g_hash_table_foreach (
        bridge->bindings, (GHFunc)
        prop_binding_block_cb, (gpointer) key);
}

void
gconf_bridge_unblock_property_bindings (GConfBridge *bridge,
                                        const gchar *key)
{
    g_return_if_fail (bridge != NULL);
    g_return_if_fail (key != NULL);

    g_hash_table_foreach (
        bridge->bindings, (GHFunc)
        prop_binding_unblock_cb, (gpointer) key);
}

/* Unbinds a property binding */
static void
prop_binding_unbind (PropBinding *binding)
{
    if (binding->delayed_mode && binding->sync_timeout_id > 0) {
                /* Perform any scheduled syncs */
        g_source_remove (binding->sync_timeout_id);

                /* The object will still be around as we have
                 * a reference */
        prop_binding_perform_scheduled_sync (binding);
    }

    gconf_client_notify_remove (bridge->client,
                    binding->val_notify_id);
    g_free (binding->key);

    while (binding->val_changes) {
        gconf_value_free (binding->val_changes->data);

        binding->val_changes = g_slist_delete_link
            (binding->val_changes, binding->val_changes);
    }

        /* The object might have been destroyed .. */
    if (binding->object) {
        g_signal_handler_disconnect (binding->object,
                         binding->prop_notify_id);

        g_object_weak_unref (binding->object,
                     prop_binding_object_destroyed, binding);
    }
}

/*
 * Window bindings
 */

/* Performs a scheduled dimensions-to-prefs sync for a window binding */
static gboolean
window_binding_perform_scheduled_sync (WindowBinding *binding)
{
    if (binding->bind_size) {
        gint width, height;
        gchar *key;
        GdkWindowState state;
        GdkWindow *window;

        window = gtk_widget_get_window (GTK_WIDGET (binding->window));
        state = gdk_window_get_state (window);

        if (state & GDK_WINDOW_STATE_MAXIMIZED) {
                        key = g_strconcat (binding->key_prefix, "_maximized", NULL);
            gconf_client_set_bool (bridge->client, key, TRUE, NULL);
            g_free (key);
        } else {
            gtk_window_get_size (binding->window, &width, &height);

                        key = g_strconcat (binding->key_prefix, "_width", NULL);
            gconf_client_set_int (bridge->client, key, width, NULL);
            g_free (key);

                        key = g_strconcat (binding->key_prefix, "_height", NULL);
            gconf_client_set_int (bridge->client, key, height, NULL);
            g_free (key);

                        key = g_strconcat (binding->key_prefix, "_maximized", NULL);
            gconf_client_set_bool (bridge->client, key, FALSE, NULL);
            g_free (key);
        }
    }

    if (binding->bind_pos) {
        gint x, y;
        gchar *key;

        gtk_window_get_position (binding->window, &x, &y);

                key = g_strconcat (binding->key_prefix, "_x", NULL);
        gconf_client_set_int (bridge->client, key, x, NULL);
        g_free (key);

                key = g_strconcat (binding->key_prefix, "_y", NULL);
        gconf_client_set_int (bridge->client, key, y, NULL);
        g_free (key);
    }

    binding->sync_timeout_id = 0;

    return FALSE;
}

#define WINDOW_BINDING_SYNC_DELAY 1 /* Delay before syncing new window
                                          dimensions to GConf, in s */

/* Called when the window han been resized or moved */
static gboolean
window_binding_configure_event_cb (GtkWindow *window,
                                   GdkEventConfigure *event,
                                   WindowBinding *binding)
{
        /* re-postpone by cancel of the previous request */
    if (binding->sync_timeout_id > 0)
        g_source_remove (binding->sync_timeout_id);

        /* Schedule a sync */
    binding->sync_timeout_id = g_timeout_add_seconds (WINDOW_BINDING_SYNC_DELAY,
        (GSourceFunc) window_binding_perform_scheduled_sync,
        binding);

    return FALSE;
}

/* Called when the window state is being changed */
static gboolean
window_binding_state_event_cb (GtkWindow *window,
                               GdkEventWindowState *event,
                               WindowBinding *binding)
{
    if (binding->sync_timeout_id > 0)
        g_source_remove (binding->sync_timeout_id);

    if (event
        && (event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0
        && (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) == 0) {
        /* the window was restored from a maximized state; make sure its
         * width and height is the one user stored before maximization */
        gint width, height;

        width = GPOINTER_TO_INT (g_object_get_data (
            G_OBJECT (window), "binding-premax-width"));
        height = GPOINTER_TO_INT (g_object_get_data (
            G_OBJECT (window), "binding-premax-height"));

        if (width && height) {
            gtk_window_resize (window, width, height);

            /* Do this only once, as it is restored
             * after loading maximized state. */
            g_object_set_data (
                G_OBJECT (window),
                "binding-premax-width", NULL);
            g_object_set_data (
                G_OBJECT (window),
                "binding-premax-height", NULL);
        }
    }

    window_binding_perform_scheduled_sync (binding);

    return FALSE;
}

/* Called when the window is being unmapped */
static gboolean
window_binding_unmap_cb (GtkWindow *window,
                         WindowBinding *binding)
{
        /* Force sync */
    if (binding->sync_timeout_id > 0)
        g_source_remove (binding->sync_timeout_id);

        /* XXX It's too late to record the window position.
         *     gtk_window_get_position() will report (0, 0). */
    binding->bind_pos = FALSE;

    window_binding_perform_scheduled_sync (binding);

    return FALSE;
}

/* Called when a window is destroyed */
static void
window_binding_window_destroyed (gpointer user_data,
                                 GObject *where_the_object_was)
{
    WindowBinding *binding;

    binding = (WindowBinding *) user_data;
    binding->window = NULL; /* Don't do anything with the window
                                 * at unbind() */

    if (binding->sync_timeout_id > 0)
        g_source_remove (binding->sync_timeout_id);

    g_hash_table_remove (bridge->bindings,
                 GUINT_TO_POINTER (binding->id));
}

/**
 * gconf_bridge_bind_window
 * @bridge: A #GConfBridge
 * @key_prefix: The prefix of the GConf keys
 * @window: A #GtkWindow
 * @bind_size: TRUE to bind the size of @window
 * @bind_pos: TRUE to bind the position of @window
 *
 * On calling this function @window will be resized to the values
 * specified by "@key_prefix<!-- -->_width" and "@key_prefix<!-- -->_height"
 * and maximixed if "@key_prefix<!-- -->_maximized is TRUE if
 * @bind_size is TRUE, and moved to the values specified by
 * "@key_prefix<!-- -->_x" and "@key_prefix<!-- -->_y" if @bind_pos is TRUE.
 * The respective GConf values will be updated when the window is resized
 * and/or moved.
 *
 * Return value: The ID of the new binding.
 **/
guint
gconf_bridge_bind_window (GConfBridge *bridge,
                          const gchar *key_prefix,
                          GtkWindow *window,
                          gboolean bind_size,
                          gboolean bind_pos)
{
    WindowBinding *binding;

    g_return_val_if_fail (bridge != NULL, 0);
    g_return_val_if_fail (key_prefix != NULL, 0);
    g_return_val_if_fail (GTK_IS_WINDOW (window), 0);

        /* Create new binding. */
    binding = g_new (WindowBinding, 1);

    binding->type = BINDING_WINDOW;
    binding->id = new_id ();
    binding->bind_size = bind_size;
    binding->bind_pos = bind_pos;
    binding->key_prefix = g_strdup (key_prefix);
    binding->window = window;
    binding->sync_timeout_id = 0;

        /* Set up GConf keys & sync window to GConf values */
    if (bind_size) {
        gchar *key;
        GConfValue *width_val, *height_val, *maximized_val;

                key = g_strconcat (key_prefix, "_width", NULL);
        width_val = gconf_client_get (bridge->client, key, NULL);
        g_free (key);

                key = g_strconcat (key_prefix, "_height", NULL);
        height_val = gconf_client_get (bridge->client, key, NULL);
        g_free (key);

                key = g_strconcat (key_prefix, "_maximized", NULL);
        maximized_val = gconf_client_get (bridge->client, key, NULL);
        g_free (key);

        if (width_val && height_val) {
            gtk_window_resize (window,
                       gconf_value_get_int (width_val),
                       gconf_value_get_int (height_val));

            gconf_value_free (width_val);
            gconf_value_free (height_val);
        } else if (width_val) {
            gconf_value_free (width_val);
        } else if (height_val) {
            gconf_value_free (height_val);
        }

        if (maximized_val) {
            if (gconf_value_get_bool (maximized_val)) {
                /* Maximize is not done immediately, but to
                 * count with proper window size, resize it
                 * before. The previous size is restored
                 * after the maximization is changed,
                 * in window_binding_state_event_cb(). */
                gint width = 0, height = 0;
                GdkScreen *screen;

                gtk_window_get_size (window, &width, &height);
                g_object_set_data (
                    G_OBJECT (window),
                    "binding-premax-width",
                    GINT_TO_POINTER (width));
                g_object_set_data (
                    G_OBJECT (window),
                    "binding-premax-height",
                    GINT_TO_POINTER (height));

                screen = gtk_window_get_screen (window);
                gtk_window_resize (window,
                    gdk_screen_get_width (screen),
                    gdk_screen_get_height (screen));

                gtk_window_maximize (window);
            }
            gconf_value_free (maximized_val);
        }
    }

    if (bind_pos) {
        gchar *key;
        GConfValue *x_val, *y_val;

                key = g_strconcat (key_prefix, "_x", NULL);
        x_val = gconf_client_get (bridge->client, key, NULL);
        g_free (key);

                key = g_strconcat (key_prefix, "_y", NULL);
        y_val = gconf_client_get (bridge->client, key, NULL);
        g_free (key);

        if (x_val && y_val) {
            gtk_window_move (window,
                     gconf_value_get_int (x_val),
                     gconf_value_get_int (y_val));

            gconf_value_free (x_val);
            gconf_value_free (y_val);
        } else if (x_val) {
            gconf_value_free (x_val);
        } else if (y_val) {
            gconf_value_free (y_val);
        }
    }

        /* Connect to window size change notifications */
    binding->configure_event_id =
        g_signal_connect (window,
                                  "configure-event",
                  G_CALLBACK
                    (window_binding_configure_event_cb),
                  binding);

    binding->window_state_event_id =
        g_signal_connect (window,
                                  "window_state_event",
                  G_CALLBACK
                    (window_binding_state_event_cb),
                  binding);
    binding->unmap_id =
        g_signal_connect (window,
                                  "unmap",
                  G_CALLBACK (window_binding_unmap_cb),
                  binding);

        /* Handle case where window gets destroyed */
    g_object_weak_ref (G_OBJECT (window),
               window_binding_window_destroyed, binding);

        /* Insert binding */
    g_hash_table_insert (bridge->bindings,
                 GUINT_TO_POINTER (binding->id), binding);

        /* Done */
    return binding->id;
}

/* Unbinds a window binding */
static void
window_binding_unbind (WindowBinding *binding)
{
    if (binding->sync_timeout_id > 0)
        g_source_remove (binding->sync_timeout_id);

    g_free (binding->key_prefix);

        /* The window might have been destroyed .. */
    if (binding->window) {
        g_signal_handler_disconnect (binding->window,
                         binding->configure_event_id);
        g_signal_handler_disconnect (binding->window,
                         binding->window_state_event_id);
        g_signal_handler_disconnect (binding->window,
                         binding->unmap_id);

        g_object_weak_unref (G_OBJECT (binding->window),
                     window_binding_window_destroyed, binding);
    }
}

/*
 * List store bindings
 */

/* Fills a GtkListStore with the string list from @value */
static void
list_store_binding_sync_pref_to_store (ListStoreBinding *binding,
                                       GConfValue *value)
{
    GSList *list, *l;
    GtkTreeIter iter;

        /* Make sure we don't enter an infinite synchronizing loop */
    g_signal_handler_block (binding->list_store,
                binding->row_inserted_id);
    g_signal_handler_block (binding->list_store,
                binding->row_deleted_id);

    gtk_list_store_clear (binding->list_store);

    list = gconf_value_get_list (value);
    for (l = list; l; l = l->next) {
        GConfValue *l_value;
        const gchar *string;

        l_value = (GConfValue *) l->data;
        string = gconf_value_get_string (l_value);

        gtk_list_store_insert_with_values (binding->list_store,
                           &iter, -1,
                           0, string,
                           -1);
    }

    g_signal_handler_unblock (binding->list_store,
                  binding->row_inserted_id);
    g_signal_handler_unblock (binding->list_store,
                  binding->row_deleted_id);
}

/* Sets a GConf value to the contents of a GtkListStore */
static gboolean
list_store_binding_sync_store_to_pref (ListStoreBinding *binding)
{
    GtkTreeModel *tree_model;
    GtkTreeIter iter;
    GSList *list;
    gint res;
    GConfValue *gconf_value;

    tree_model = GTK_TREE_MODEL (binding->list_store);

        /* Build list */
    list = NULL;
    res = gtk_tree_model_get_iter_first (tree_model, &iter);
    while (res) {
        gchar *string;
        GConfValue *tmp_value;

        gtk_tree_model_get (tree_model, &iter,
                    0, &string, -1);

        tmp_value = gconf_value_new (GCONF_VALUE_STRING);
        gconf_value_set_string (tmp_value, string);

        list = g_slist_append (list, tmp_value);

        res = gtk_tree_model_iter_next (tree_model, &iter);
    }

        /* Create value */
    gconf_value = gconf_value_new (GCONF_VALUE_LIST);
    gconf_value_set_list_type (gconf_value, GCONF_VALUE_STRING);
    gconf_value_set_list_nocopy (gconf_value, list);

        /* Set */
    gconf_client_set (bridge->client, binding->key, gconf_value, NULL);

        /* Store until change notification comes in, so that we are able
         * to ignore it */
    binding->val_changes = g_slist_append (binding->val_changes,
                           gconf_value);

    binding->sync_idle_id = 0;

    g_object_unref (binding->list_store);

    return FALSE;
}

/* Pref changed: sync */
static void
list_store_binding_pref_changed (GConfClient *client,
                                 guint cnxn_id,
                                 GConfEntry *entry,
                                 gpointer user_data)
{
    GConfValue *gconf_value;
    ListStoreBinding *binding;
    GSList *l;

    gconf_value = gconf_entry_get_value (entry);
    if (!gconf_value)
        return; /* NULL means that the value has been unset */

    binding = (ListStoreBinding *) user_data;

        /* Check that this notification is not caused by
         * sync_store_to_pref() */
    l = g_slist_find_custom (binding->val_changes,
                 gconf_value,
                 (GCompareFunc) gconf_value_compare);
    if (l) {
        gconf_value_free (l->data);

        binding->val_changes = g_slist_delete_link
            (binding->val_changes, l);

        return;
    }

    list_store_binding_sync_pref_to_store (binding, gconf_value);
}

/* Called when an object is destroyed */
static void
list_store_binding_store_destroyed (gpointer user_data,
                                    GObject *where_the_object_was)
{
    ListStoreBinding *binding;

    binding = (ListStoreBinding *) user_data;
    binding->list_store = NULL; /* Don't do anything with the store
                                     * at unbind() */

    g_hash_table_remove (bridge->bindings,
                 GUINT_TO_POINTER (binding->id));
}

/* List store changed: Sync */
static void
list_store_binding_store_changed_cb (ListStoreBinding *binding)
{
    if (binding->sync_idle_id == 0) {
        g_object_ref (binding->list_store);

        binding->sync_idle_id = g_idle_add
            ((GSourceFunc) list_store_binding_sync_store_to_pref,
             binding);
    }
}

/**
 * gconf_bridge_bind_string_list_store
 * @bridge: A #GConfBridge
 * @key: A GConf key to be bound
 * @list_store: A #GtkListStore
 *
 * On calling this function single string column #GtkListStore @list_store
 * will be kept synchronized with the GConf string list value pointed to by
 * @key. On calling this function @list_store will be populated with the
 * strings specified by the value of @key.
 *
 * Return value: The ID of the new binding.
 **/
guint
gconf_bridge_bind_string_list_store (GConfBridge *bridge,
                                     const gchar *key,
                                     GtkListStore *list_store)
{
    GtkTreeModel *tree_model;
    gboolean have_one_column, is_string_column;
    ListStoreBinding *binding;
    GConfValue *val;

    g_return_val_if_fail (bridge != NULL, 0);
    g_return_val_if_fail (key != NULL, 0);
    g_return_val_if_fail (GTK_IS_LIST_STORE (list_store), 0);

        /* Check list store suitability */
    tree_model = GTK_TREE_MODEL (list_store);
    have_one_column = (gtk_tree_model_get_n_columns (tree_model) == 1);
    is_string_column = (gtk_tree_model_get_column_type
                    (tree_model, 0) == G_TYPE_STRING);
    if (G_UNLIKELY (!have_one_column || !is_string_column)) {
                g_warning ("gconf_bridge_bind_string_list_store: Only "
                           "GtkListStores with exactly one string column are "
                           "supported.");

        return 0;
    }

        /* Create new binding. */
    binding = g_new (ListStoreBinding, 1);

    binding->type = BINDING_LIST_STORE;
    binding->id = new_id ();
    binding->key = g_strdup (key);
    binding->val_changes = NULL;
    binding->list_store = list_store;
    binding->sync_idle_id = 0;

        /* Watch GConf key */
    binding->val_notify_id =
        gconf_client_notify_add (bridge->client, key,
                     list_store_binding_pref_changed,
                     binding, NULL, NULL);

        /* Connect to ListStore change notifications */
    binding->row_inserted_id =
                g_signal_connect_swapped (list_store, "row-inserted",
                      G_CALLBACK
                      (list_store_binding_store_changed_cb),
                      binding);
    binding->row_changed_id =
                g_signal_connect_swapped (list_store, "row-changed",
                      G_CALLBACK
                      (list_store_binding_store_changed_cb),
                      binding);
    binding->row_deleted_id =
                g_signal_connect_swapped (list_store, "row-deleted",
                      G_CALLBACK
                      (list_store_binding_store_changed_cb),
                      binding);
    binding->rows_reordered_id =
                g_signal_connect_swapped (list_store, "rows-reordered",
                      G_CALLBACK
                      (list_store_binding_store_changed_cb),
                      binding);

        /* Sync object to value from GConf, if set */
    val = gconf_client_get (bridge->client, key, NULL);
    if (val) {
        list_store_binding_sync_pref_to_store (binding, val);
        gconf_value_free (val);
    }

        /* Handle case where watched object gets destroyed */
    g_object_weak_ref (G_OBJECT (list_store),
               list_store_binding_store_destroyed, binding);

        /* Insert binding */
    g_hash_table_insert (bridge->bindings,
                 GUINT_TO_POINTER (binding->id), binding);

        /* Done */
    return binding->id;
}

/* Unbinds a list store binding */
static void
list_store_binding_unbind (ListStoreBinding *binding)
{
        /* Perform any scheduled syncs */
    if (binding->sync_idle_id > 0) {
        g_source_remove (binding->sync_idle_id);

                /* The store will still be around as we added a reference */
        list_store_binding_sync_store_to_pref (binding);
    }

    g_free (binding->key);

    while (binding->val_changes) {
        gconf_value_free (binding->val_changes->data);

        binding->val_changes = g_slist_delete_link
            (binding->val_changes, binding->val_changes);
    }

        /* The store might have been destroyed .. */
    if (binding->list_store) {
        g_signal_handler_disconnect (binding->list_store,
                         binding->row_inserted_id);
        g_signal_handler_disconnect (binding->list_store,
                         binding->row_changed_id);
        g_signal_handler_disconnect (binding->list_store,
                         binding->row_deleted_id);
        g_signal_handler_disconnect (binding->list_store,
                         binding->rows_reordered_id);

        g_object_weak_unref (G_OBJECT (binding->list_store),
                     list_store_binding_store_destroyed,
                     binding);
    }
}

/*
 * Generic unbinding
 */

/* Unbinds a binding */
static void
unbind (Binding *binding)
{
        /* Call specialized unbinding function */
    switch (binding->type) {
    case BINDING_PROP:
        prop_binding_unbind ((PropBinding *) binding);
        break;
    case BINDING_WINDOW:
        window_binding_unbind ((WindowBinding *) binding);
        break;
    case BINDING_LIST_STORE:
        list_store_binding_unbind ((ListStoreBinding *) binding);
        break;
    default:
                g_warning ("Unknown binding type '%d'\n", binding->type);
        break;
    }

    g_free (binding);
}

/**
 * gconf_bridge_unbind
 * @bridge: A #GConfBridge
 * @binding_id: The ID of the binding to be removed
 *
 * Removes the binding with ID @binding_id.
 **/
void
gconf_bridge_unbind (GConfBridge *bridge,
                     guint binding_id)
{
    g_return_if_fail (bridge != NULL);
    g_return_if_fail (binding_id > 0);

        /* This will trigger the hash tables value destruction
         * function, which will take care of further cleanup */
    g_hash_table_remove (bridge->bindings,
                 GUINT_TO_POINTER (binding_id));
}

/*
 * Error handling
 */

/* This is the same dialog as used in eel */
static void
error_handler (GConfClient *client,
               GError *error)
{
    static gboolean shown_dialog = FALSE;

        g_warning ("GConf error:\n  %s", error->message);

    if (!shown_dialog) {
        gchar *message;
        GtkWidget *dlg;

                message = g_strdup_printf (_("GConf error: %s"),
                       error->message);
        dlg = gtk_message_dialog_new (NULL, 0,
                          GTK_MESSAGE_ERROR,
                          GTK_BUTTONS_OK,
                                              "%s",
                          message);
        g_free (message);

        gtk_message_dialog_format_secondary_text
            (GTK_MESSAGE_DIALOG (dlg),
                         _("All further errors shown only on terminal."));
                gtk_window_set_title (GTK_WINDOW (dlg), "");

        gtk_dialog_run (GTK_DIALOG (dlg));

        gtk_widget_destroy (dlg);

        shown_dialog = TRUE;
    }
}

/**
 * gconf_bridge_install_default_error_handler
 *
 * Sets up the default error handler. Any unhandled GConf errors will
 * automatically be handled by presenting the user an error dialog.
 **/
void
gconf_bridge_install_default_error_handler (void)
{
    gconf_client_set_global_default_error_handler (error_handler);
}