aboutsummaryrefslogblamecommitdiffstats
path: root/lib/ephy-dialog.c
blob: 0ed3df6fd500f8a66e87420dba14497e3d469997 (plain) (tree)
1
2
3
  
                                                
                                             













                                                                              

        

   



                    


                        
                     
                                 
                       
 
                   

                                
                               



                              




                           

                             



            


                        

                    
                  
             


              
                       
                           

                                       
                          

                               
                           


                            

               

                                                                                                                    

                        


                          

                          
                       
                                  
                           
                             
                                  



                       







                                            


                                                            




                                         
                              
 
                      
         















                                                                
         

                    

 

                                                
 

                                                             
 



                                                        
 


                                                
 
                                                                                    
 








                                                                                   

 
           
                                                       
 
                   
 
                                
         
                                   



                                                                                 
                                


                                                                                    
                                  


                                                                                    
                                    







                                                                                        
                                                                                            





                                                       





                                                                                             























































                                                                                       
                                                           
 


                                                           
 









                                                                               









                                                                                       
         

                                                 


            
                                                                             


         


                                                      
                                       

                             

                            

                                                                           

                                                     
                                                         















                                                                 
           
                                                                   
 
                  
 
                                                              
 










                                                                                               
         
                                               


            
                                                                                    



           
                                                              
 

                        
 
                                                             
 
                                                                       
 







                                                                                   
         
                                             


            
                                                                                   



           
                                                               
 


                                                               
 
                                                                                 
 








                                                                                     


           
                                                       
 
                                      
         
                                                                                            
         

                                  
         











                                                                      





                                                                
         
 
 








                                                                 
         










                                                                                   

         
                         
         
                                     

         



                                                                                          
 
                      

 
          
                                                              
 

                        
                        
 
                        
         
                                                 
 

                        
                                                                                         



                      
                                                                 
                 
 


            
                                                

         
                     

 

                                                 
 


                                             
 

                                            
 


                                                          
         
                                                                  
         
                                                 
         




                                                                          

         
                     


           
                                                                   
 
                       
 
                                                           
 
                                 
         






                                                                               




















                                                                                       
         
                                               
         
                                                
         
            
         
                                                                                 
         

                      
         


                                              
                       
         
 




                                                                       

 

                                                                         
 



                                
 






                                                                            
 







                                                                            
                       

         








                                                                    


           
                                                                    
 

                        
 
                                                             
 
                                                                                    
 
                                                    
         
                                                    
         
                                                 
         
                                              


            


                                                                                   

         
                                
 
                                                                      


           
                                                                     
 


































                                                                                











                                                               
                                                


                              
                              
         

                                                                                       

         




                                                                                               
         
                                                   
         

                               
 
 


                                                               
                                           


           
                                                              





                                                                      
                                           

 

                                          
 


                                                                                     
 





                                         
 





                                                                           



                                                                          
                                                                        
 
                                                   
 


                                           
 

                                            


           
                                                             
 
                           
 
                                                     
 
                                                                          
 

                                       
         
                                             

         





                                                                         


           
                                                  
 
                                           


           
                                                                      
 
                     
 
                                                
 
                                                     
 

                                  
                                     
                                                                             
                                                                               



                                                          
                                                                  

                                                               

                                                                          

                                                                             


                                   
                                                                             

                                                                             
                              
                                 
                                                                             




                                                                             

                                
                                                                
                              



           
                                                                         
 
                                                
 
                                                                                                         


           
                                                                                     


              
                                                   
         
                                                              
 
                                            
                                      






                                                                     
                                                    
                 

                                                        
                 
                                                           
                 

                                                          
                 
                                                            
                 

                                                           
                 
                                                             
                 

                                                            
                 
                                                        
                 

                                                        
                 
                    
                 

                                                         
                 

                                                 
                 
                                                                  
                 




                                                                                                



           
                                                                
 














                                                                                                       


                            
 


                                                                







                                                                
         

                                                                              
         



                                           


           
                                       
 

                                                    








                                                                 

                                                                  
                                              
 

                                                      


           
                                                         
 
                                                                               
 



                                             


           


                                                     

                                   
 
                       
 

                                                                      
















                                                               


           
                              
 


                                                 
 


                                                                                             
 
                                    
 
                                         






                                                                                 
 
                                                               

 


                                          
 


                                                                          

 
    
                                         
                                     

                                         
 
                           


                        


                                        

                                     
                                                            

         
                                               

 



                                                
 
                           
 

                                        
 

                                
 





                                              
 

                                                 
 
                                                
 



                                     
 
                                      
         


                                                                            
         

 

                                               

                                                 
 
                                 
                   
 
                                                                    
 


                                
         
                                   
 
                                                      




                                                                     

                                                    
         

                    
 
 

                                          

                                                            

                                          

                                                                
                                                                         
 
 




                                                                

 








                                                        













                                                                       

                                    
 

                                  
























                                                                                               
                                                                  

 


                                                 
 
                           
 



                                                  

 



                                               
 



                                                 
 
                                          

 



                                               
 



                                                 
 
                                          


           
                                   
 
                              
         

                                                                        

         
                            
 
                      

 

                                     
 







                                                        
                                               
 

                                                                                 

 

                                     
 







                                                          

 

                                      
 






                                                         

 


                                           
 

                                      
                                                             

 




                                              
 









                                                                                    


                                                                                     
         

 




                                            
 








                                                                         



                                                                                    
         

 

                                               
 
                                                            
 
                                                        
 







                                                              










                                                                         






                                                                                  
 






                                                                                   
 







                                                                                         











                                                                            

                                                           


                                                                         
 
/*
 *  Copyright (C) 2000-2003 Marco Pesenti Gritti
 *  Copyright (C) 2003, 2004 Christian Persch
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 *  $Id$
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "ephy-dialog.h"
#include "ephy-glade.h"
#include "ephy-state.h"
#include "ephy-gui.h"
#include "eel-gconf-extensions.h"
#include "ephy-debug.h"

#include <stdlib.h>
#include <string.h>
#include <gtk/gtktogglebutton.h>
#include <gtk/gtkradiobutton.h>
#include <gtk/gtkcombobox.h>
#include <gtk/gtkspinbutton.h>
#include <gtk/gtkeditable.h>
#include <gtk/gtkentry.h>

enum
{
    PROP_0,
    PROP_PARENT_WINDOW,
    PROP_MODAL,
    PROP_PERSIST_POSITION
};

typedef enum
{
    PT_TOGGLEBUTTON,
    PT_RADIOBUTTON,
    PT_SPINBUTTON,
    PT_COMBOBOX,
    PT_EDITABLE,
    PT_UNKNOWN
} WidgetType;

typedef struct
{
    const char *id;
    EphyDialog *dialog;
    char *pref;
    EphyDialogApplyType apply_type;
    GtkWidget *widget;
    WidgetType widget_type;
    GType data_type;
    GList *string_enum;
    int data_col;
    gboolean loaded;
    gboolean sane_state;
} PropertyInfo;

#define EPHY_DIALOG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_DIALOG, EphyDialogPrivate))

struct EphyDialogPrivate
{
    char *name;

    GHashTable *props;
    GtkWidget *parent;
    GtkWidget *dialog;
    gboolean modal;
    gboolean has_default_size;
    gboolean disposing;
    gboolean initialized;
    gboolean persist_position;
};

#define SPIN_DELAY 0.20

enum
{
    CHANGED,
    LAST_SIGNAL
};

static guint signals [LAST_SIGNAL] = { 0, };

static void ephy_dialog_class_init (EphyDialogClass *klass);
static void ephy_dialog_init       (EphyDialog *window);

static GObjectClass *parent_class = NULL;

GType
ephy_dialog_get_type (void)
{
    static GType type = 0;

    if (type == 0)
    {
        static const GTypeInfo our_info =
        {
            sizeof (EphyDialogClass),
            NULL, /* base_init */
            NULL, /* base_finalize */
            (GClassInitFunc) ephy_dialog_class_init,
            NULL,
            NULL, /* class_data */
            sizeof (EphyDialog),
            0, /* n_preallocs */
            (GInstanceInitFunc) ephy_dialog_init
        };

        type = g_type_register_static (G_TYPE_OBJECT,
                           "EphyDialog",
                           &our_info, 0);
    }

    return type;
}

static PropertyInfo *
lookup_info (EphyDialog *dialog, const char *id)
{
    return g_hash_table_lookup (dialog->priv->props, id);
}

static void
set_sensitivity (PropertyInfo *info, gboolean sensitive)
{
    g_return_if_fail (info->widget != NULL);

    if (info->widget_type == PT_RADIOBUTTON)
    {
        GSList *list, *l;

        list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (info->widget));

        for (l = list; l != NULL; l = l->next)
        {
            gtk_widget_set_sensitive (GTK_WIDGET (l->data), sensitive);
        }
    }
    else
    {
        gtk_widget_set_sensitive (info->widget, sensitive);
    }
}

static void
set_value_from_pref (PropertyInfo *info, GValue *value)
{
    char *text;

    switch (info->data_type)
    {
        case G_TYPE_STRING:
            g_value_init (value, G_TYPE_STRING);
            text = eel_gconf_get_string (info->pref);
            g_value_take_string (value, text ? text : g_strdup (""));
            break;
        case G_TYPE_INT:
            g_value_init (value, G_TYPE_INT);
            g_value_set_int (value, eel_gconf_get_integer (info->pref));
            break;
        case G_TYPE_FLOAT:
            g_value_init (value, G_TYPE_FLOAT);
            g_value_set_float (value, eel_gconf_get_float (info->pref));
            break;
        case G_TYPE_BOOLEAN:
            g_value_init (value, G_TYPE_BOOLEAN);
            g_value_set_boolean (value, eel_gconf_get_boolean (info->pref));
            break;
        default:
            g_warning ("Unsupported value read from pref %s\n", info->pref);
            break;
    }

    LOG ("id[%s], pref[%s] = %s", info->id, info->pref, g_strdup_value_contents (value))
}

static void
set_pref_from_value (PropertyInfo *info, GValue *value)
{
    const char *pref = info->pref;

    if (!G_VALUE_HOLDS (value, info->data_type))
    {
        g_warning ("Value type mismatch for id[%s], pref[%s]", info->id, info->pref);
        return;
    }

    switch (info->data_type)
    {
        case G_TYPE_STRING:
            eel_gconf_set_string (pref, g_value_get_string (value));
            break;
        case G_TYPE_INT:
            eel_gconf_set_integer (pref, g_value_get_int (value));
            break;
        case G_TYPE_FLOAT:
            eel_gconf_set_float (pref, g_value_get_float (value));
            break;
        case G_TYPE_BOOLEAN:
            eel_gconf_set_boolean (pref, g_value_get_boolean (value));
            break;
        default:
            break;
    }
}

static void
set_value_from_editable (PropertyInfo *info, GValue *value)
{
    char *text;
    gboolean free_text = TRUE;

    g_return_if_fail (GTK_IS_EDITABLE (info->widget));

    text = gtk_editable_get_chars (GTK_EDITABLE (info->widget), 0, -1);

    g_value_init (value, info->data_type);
    switch (info->data_type)
    {
        case G_TYPE_STRING:
            g_value_take_string (value, text);
            free_text = FALSE;
            break;
        /* FIXME : handle possible errors in the input for int and float */
        case G_TYPE_INT:
            g_value_set_int (value, atoi (text));
            break;
        case G_TYPE_FLOAT:
            g_value_set_float (value, strtod (text, NULL));
            break;
        default:
            g_warning ("Unsupported value type for editable %s", info->id);
            break;
    }

    if (free_text)
    {
        g_free (text);
    }
}

static void
set_value_from_combobox (PropertyInfo *info, GValue *value)
{
    int index;

    g_return_if_fail (GTK_IS_COMBO_BOX (info->widget));

    index = gtk_combo_box_get_active (GTK_COMBO_BOX (info->widget));
    g_return_if_fail (index >= 0);

    if (info->data_col != -1)
    {
        GtkTreeModel *model;
        GtkTreeIter iter;

        model = gtk_combo_box_get_model (GTK_COMBO_BOX (info->widget));

        if (gtk_tree_model_iter_nth_child (model, &iter, NULL, index))
        {
            gtk_tree_model_get_value (model, &iter, info->data_col, value);
        }
        else
        {
            g_warning ("Invalid index in combo model for %s\n", info->id);
        }
    }
    else if (info->data_type == G_TYPE_INT)
    {
        g_value_init (value, G_TYPE_INT);
        g_value_set_int (value, index);
    }
    else
    {
        g_warning ("Unsupported data type for combo %s\n", info->id);
    }
}

static int
get_radio_button_active_index (GtkWidget *radiobutton)
{
    GtkToggleButton *toggle_button;
    GSList *list;
    int index, i, length;

    /* get group list */
    list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton));
    length = g_slist_length (list);

    /* iterate over list to find active button */
    for (i = 0; list != NULL; i++, list = list->next)
    {
        /* get button and text */
        toggle_button = GTK_TOGGLE_BUTTON (list->data);
        if (gtk_toggle_button_get_active (toggle_button))
        {
            break;
        }
    }

    /* check we didn't run off end */
    g_assert (list != NULL);

    /* return index (reverse order!) */
    return index = (length - 1) - i;
}

static void
set_value_from_radiobuttongroup (PropertyInfo *info, GValue *value)
{
    int index;

    g_return_if_fail (GTK_IS_RADIO_BUTTON (info->widget));

    index = get_radio_button_active_index (info->widget);
    g_return_if_fail (index >= 0);

    g_value_init (value, info->data_type);
    if (info->data_type == G_TYPE_STRING)
    {
        g_return_if_fail (info->string_enum != NULL);

        g_value_set_string (value, (char*) g_list_nth_data (info->string_enum, index));
    }
    else if (info->data_type == G_TYPE_INT)
    {
        g_value_set_int (value, index);
    }
    else
    {
        g_warning ("unsupported data type for radio button %s\n", info->id);
    }
}

static void
set_value_from_spin_button (PropertyInfo *info, GValue *value)
{
    gdouble f;
    gboolean is_int;

    g_return_if_fail (GTK_IS_SPIN_BUTTON (info->widget));

    f = gtk_spin_button_get_value (GTK_SPIN_BUTTON (info->widget));

    is_int = (gtk_spin_button_get_digits (GTK_SPIN_BUTTON(info->widget)) == 0);

    g_value_init (value, info->data_type);
    if (info->data_type == G_TYPE_INT && is_int)
    {
        g_value_set_int (value, (int) f);
    }
    else if (info->data_type == G_TYPE_FLOAT)
    {
        g_value_set_float (value, f);
    }
    else
    {
        g_warning ("Unsupported data type for spin button %s\n", info->id);
    }
}

static void
set_value_from_togglebutton (PropertyInfo *info, GValue *value)
{
    gboolean active;

    g_return_if_fail (GTK_IS_TOGGLE_BUTTON (info->widget));

    active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (info->widget));

    g_value_init (value, info->data_type);
    if (info->data_type == G_TYPE_BOOLEAN)
    {
        g_value_set_boolean (value, active);
    }
    else
    {
        g_warning ("Unsupported data type for toggle button %s\n", info->id);
    }
}

static void
set_value_from_info (PropertyInfo *info, GValue *value)
{
    if (info->sane_state == FALSE)
    {
        g_warning ("id[%s] has insane state when trying to get value!\n", info->id);
    }

    switch (info->widget_type)
    {
        case PT_SPINBUTTON:
            set_value_from_spin_button (info, value);
            break;
        case PT_RADIOBUTTON:
            set_value_from_radiobuttongroup (info, value);
            break;
        case PT_TOGGLEBUTTON:
            set_value_from_togglebutton (info, value);
            break;
        case PT_EDITABLE:
            set_value_from_editable (info, value);
            break;
        case PT_COMBOBOX:
            set_value_from_combobox (info, value);
            break;
        default:
            g_warning ("Unsupported widget type\n");
            break;
    }
}

static void
set_editable_from_value (PropertyInfo *info, const GValue *value)
{
    char *text = NULL;
    int pos;

    g_return_if_fail (GTK_IS_EDITABLE (info->widget));

    switch (info->data_type)
    {
        case G_TYPE_STRING:
            text = g_value_dup_string (value);
            break;
        case G_TYPE_INT:
            text = g_strdup_printf ("%d", g_value_get_int (value));
            break;
        case G_TYPE_FLOAT:
            text = g_strdup_printf ("%.2f", g_value_get_float (value));
            break;
        default:
            break;
    }

    if (text == NULL)
    {
        text = g_strdup ("");
    }

    info->sane_state = TRUE;

    gtk_editable_delete_text (GTK_EDITABLE (info->widget), 0, -1);
    gtk_editable_insert_text (GTK_EDITABLE (info->widget), text, strlen (text), &pos);

    g_free (text);
}

static int
get_index_from_value (const GValue *value, GList *string_enum)
{
    int index = -1;
    const char *val;
    GList *s = NULL;

    if (string_enum)
    {
        val = g_value_get_string (value);

        if (val)
        {
            s = g_list_find_custom (string_enum, val, (GCompareFunc) strcmp);
        }

        if (s)
        {
            index = g_list_position (string_enum, s);
        }

    }
    else
    {
        index = g_value_get_int (value);
    }

    return index;
}

static gboolean
compare_values (const GValue *a, const GValue *b)
{
    if (G_VALUE_HOLDS (a, G_TYPE_STRING))
    {
        const char *ta, *tb;

        ta = g_value_get_string (a);
        tb = g_value_get_string (b);

        return (ta && tb && strcmp (ta, tb) == 0);
    }
    else if (G_VALUE_HOLDS (a, G_TYPE_INT))
    {
        return g_value_get_int (a) == g_value_get_int (b);
    }
    else if (G_VALUE_HOLDS (a, G_TYPE_FLOAT))
    {
        return g_value_get_float (a) == g_value_get_float (b);
    }
    else if (G_VALUE_HOLDS (a, G_TYPE_BOOLEAN))
    {
        return g_value_get_boolean (a) == g_value_get_boolean (b);
    }

    return FALSE;
}

static void
set_combo_box_from_value (PropertyInfo *info, const  GValue *value)
{
    int index = -1;

    g_return_if_fail (GTK_IS_COMBO_BOX (info->widget));

    if (info->data_col != -1)
    {
        GValue data = { 0, };
        GtkTreeModel *model;
        GtkTreeIter iter;
        gboolean valid, found = FALSE;

        model = gtk_combo_box_get_model (GTK_COMBO_BOX (info->widget));

        valid = gtk_tree_model_get_iter_first (model, &iter);
        while (valid)
        {
            gtk_tree_model_get_value (model, &iter, info->data_col, &data);
            found = compare_values (&data, value);
            if (found) break;

            g_value_unset (&data);
            valid = gtk_tree_model_iter_next (model, &iter);
        }

        if (found)
        {
            GtkTreePath *path;
            gint *indices;

            path = gtk_tree_model_get_path (model, &iter);
            indices = gtk_tree_path_get_indices (path);
            index = indices[0];
            gtk_tree_path_free (path);
        }
    }
    else if (info->data_type == G_TYPE_INT)
    {
        index = g_value_get_int (value);
    }
    else
    {
        g_warning ("Unsupported data type for combo box %s\n", info->id);
    }

    if (index < 0)
    {
        info->sane_state = FALSE;

        g_return_if_fail (index >= 0);
        return;
    }

    LOG ("index[%s] is %d", info->id, index)

    info->sane_state = TRUE;

    gtk_combo_box_set_active (GTK_COMBO_BOX (info->widget), index);
}

static void
set_radiobuttongroup_from_value (PropertyInfo *info, const GValue *value)
{
    GtkToggleButton *button;
    GSList *list;
    gint length;
    int index;

    g_return_if_fail (GTK_IS_RADIO_BUTTON (info->widget));

    list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (info->widget));

    length = g_slist_length (list);

    index = get_index_from_value (value, info->string_enum);

    /* new buttons are *prepended* to the list, so button added as first
     * has last position in the list */
    index = (length - 1) - index;

    if (index < 0 || index >= length)
    {
        info->sane_state = FALSE;
        g_return_if_fail (index >= 0 && index < length);
        return;
    }

    button = GTK_TOGGLE_BUTTON (g_slist_nth_data (list, index));
    g_return_if_fail (button != NULL);

    info->sane_state = TRUE;

    if (gtk_toggle_button_get_active (button) == FALSE)
    {
        gtk_toggle_button_set_active (button, TRUE);
    }
}

static void
set_spin_button_from_value (PropertyInfo *info, const GValue *value)
{
    gdouble f = 0.0;
    gboolean is_int;

    g_return_if_fail (GTK_IS_SPIN_BUTTON (info->widget));

    is_int = (gtk_spin_button_get_digits (GTK_SPIN_BUTTON (info->widget)) == 0);

    if (info->data_type == G_TYPE_INT && is_int)
    {
        f = (float) g_value_get_int (value);
    }
    else if (info->data_type == G_TYPE_FLOAT)
    {
        f = g_value_get_float (value);
    }
    else
    {
        info->sane_state = FALSE;
        g_warning ("Unsupported data type for spin button %s\n", info->id);
        return;
    }

    info->sane_state = TRUE;

    gtk_spin_button_set_value (GTK_SPIN_BUTTON (info->widget), f);
}

static void
set_togglebutton_from_value (PropertyInfo *info, const GValue *value)
{
    gboolean active;

    g_return_if_fail (GTK_IS_TOGGLE_BUTTON (info->widget));
    g_return_if_fail (info->data_type == G_TYPE_BOOLEAN);

    active = g_value_get_boolean (value);

    info->sane_state = TRUE;

    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (info->widget), active);
}

static void
set_info_from_value (PropertyInfo *info, const GValue *value)
{
    if (!G_VALUE_HOLDS (value, info->data_type))
    {
        g_warning ("Incompatible value types for id %s\n", info->id);
        return;
    }

    switch (info->widget_type)
    {
        case PT_SPINBUTTON:
            set_spin_button_from_value (info, value);
            break;
        case PT_RADIOBUTTON:
            set_radiobuttongroup_from_value (info, value);
            break;
        case PT_TOGGLEBUTTON:
            set_togglebutton_from_value (info, value);
            break;
        case PT_EDITABLE:
            set_editable_from_value (info, value);
            break;
        case PT_COMBOBOX:
            set_combo_box_from_value (info, value);
            break;
        default:
            g_warning ("Unknown widget type\n");
            break;
    }
}

/* widget changed callbacks */

static void
set_pref_from_info_and_emit (PropertyInfo *info)
{
    GValue value = { 0, };

    if (!info->sane_state)
    {
        g_warning ("Not emitting/persisting insane state of id[%s]", info->id);
        return;
    }

    set_value_from_info (info, &value);

    g_signal_emit (info->dialog, signals[CHANGED], g_quark_from_string (info->id), &value);

    if (info->apply_type == PT_AUTOAPPLY && info->pref != NULL)
    {
        set_pref_from_value (info, &value);
    }

    g_value_unset (&value);
}

static void
togglebutton_clicked_cb (GtkWidget *widget, PropertyInfo *info)
{
    set_pref_from_info_and_emit (info);
}

static void
radiobutton_clicked_cb (GtkWidget *widget, PropertyInfo *info)
{
    if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget)))
    {
        return;
    }

    set_pref_from_info_and_emit (info);
}

static gboolean
spinbutton_timeout_cb (PropertyInfo *info)
{
    GTimer *spin_timer;

    spin_timer = (GTimer *) g_object_get_data (G_OBJECT (info->widget), "timer");

    /* timer still valid? */
    if (spin_timer == NULL)
    {
        /* don't call me again */
        return FALSE;
    }

    /* okay, we're ready to set */
    if (g_timer_elapsed (spin_timer, NULL) >= SPIN_DELAY)
    {
        /* kill off the timer */
        g_timer_destroy (spin_timer);
        g_object_set_data (G_OBJECT (info->widget), "timer", NULL);

        /* HACK update the spinbutton here so that the
         * changes made directly in the entry are accepted
         * and set in the pref. Otherwise the old value is used */
        gtk_spin_button_update (GTK_SPIN_BUTTON (info->widget));

        set_pref_from_info_and_emit (info);

        /* done, don't run again */
        return FALSE;
    }

    /* not elapsed yet, call me again */
    return TRUE;
}

static void
spinbutton_changed_cb (GtkWidget *widget, PropertyInfo *info)
{
    GTimer *spin_timer;

    if (info->apply_type != PT_AUTOAPPLY) return;

    spin_timer = g_object_get_data (G_OBJECT (info->widget), "timer");

    /* destroy an existing timer */
    if (spin_timer != NULL)
    {
        g_timer_destroy (spin_timer);
    }

    /* start tnew timer */
    spin_timer = g_timer_new();
    g_timer_start (spin_timer);
    g_object_set_data (G_OBJECT (info->widget), "timer", spin_timer);

    g_timeout_add (50, (GSourceFunc) spinbutton_timeout_cb, info);
}

static void
changed_cb (GtkWidget *widget, PropertyInfo *info)
{
    set_pref_from_info_and_emit (info);
}

static void
connect_signals (gpointer key, PropertyInfo *info, EphyDialog *dialog)
{
    GSList *list;

    g_return_if_fail (info->widget != NULL);

    if (info->apply_type != PT_AUTOAPPLY) return;

    switch (info->widget_type)
    {
        case PT_TOGGLEBUTTON:
            g_signal_connect (G_OBJECT (info->widget), "clicked",
                      G_CALLBACK (togglebutton_clicked_cb),
                      (gpointer)info);
            break;
        case PT_RADIOBUTTON:
            list = gtk_radio_button_get_group
                (GTK_RADIO_BUTTON (info->widget));
            for (; list != NULL; list = list->next)
            {
                g_signal_connect
                    (G_OBJECT (list->data), "clicked",
                     G_CALLBACK (radiobutton_clicked_cb),
                     info);
            }
            break;
        case PT_SPINBUTTON:
            g_signal_connect (G_OBJECT (info->widget), "changed",
                      G_CALLBACK (spinbutton_changed_cb),
                      info);
            break;
        case PT_COMBOBOX:
            g_signal_connect (G_OBJECT (info->widget), "changed",
                      G_CALLBACK (changed_cb), info);
            break;
        case PT_EDITABLE:
            g_signal_connect (G_OBJECT (info->widget), "changed",
                      G_CALLBACK (changed_cb), info);
            break;
        case PT_UNKNOWN:
            g_warning ("Unsupported widget type\n");
            break;
    }
}

static void
disconnect_signals (gpointer key, PropertyInfo *info, EphyDialog *dialog)
{
    g_return_if_fail (info->widget != NULL);

    g_signal_handlers_disconnect_matched (info->widget, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, info);
}

static void
init_props (EphyDialog *dialog, const EphyDialogProperty *properties, GladeXML *gxml)
{
    int i;

    for (i = 0 ; properties[i].id != NULL; i++)
    {
        PropertyInfo *info = g_new0 (PropertyInfo, 1);

        info->id = properties[i].id;
        info->dialog = dialog;
        info->pref = g_strdup (properties[i].pref);
        info->apply_type = properties[i].apply_type;
        info->string_enum = NULL;
        info->data_col = -1;

        info->widget = glade_xml_get_widget (gxml, info->id);
        
        if (GTK_IS_COMBO_BOX (info->widget))
        {
            info->widget_type = PT_COMBOBOX;
            info->data_type = G_TYPE_INT;
        }
        else if (GTK_IS_SPIN_BUTTON (info->widget))
        {
            info->widget_type = PT_SPINBUTTON;
            info->data_type = G_TYPE_INT;
        }
        else if (GTK_IS_RADIO_BUTTON (info->widget))
        {
            info->widget_type = PT_RADIOBUTTON;
            info->data_type = G_TYPE_INT;
        }
        else if (GTK_IS_TOGGLE_BUTTON (info->widget))
        {
            info->widget_type = PT_TOGGLEBUTTON;
            info->data_type = G_TYPE_BOOLEAN;
        }
        else if (GTK_IS_EDITABLE (info->widget))
        {
            info->widget_type = PT_EDITABLE;
            info->data_type = G_TYPE_STRING;
        }
        else
        {
            info->widget_type = PT_UNKNOWN;
            info->data_type = G_TYPE_INVALID;
        }

        if (properties[i].data_type != 0)
        {
            info->data_type = properties[i].data_type;
        }

        info->loaded = FALSE;
        info->sane_state = FALSE;

        g_hash_table_insert (dialog->priv->props, (char *) info->id, info);     
    }
}

static void
load_info (gpointer key, PropertyInfo *info, EphyDialog *dialog)
{
    GValue value = { 0, };

    g_return_if_fail (info->widget != NULL);

    if (info->pref != NULL)
    {
        set_value_from_pref (info, &value);
        set_info_from_value (info, &value);

        g_signal_emit (info->dialog, signals[CHANGED], g_quark_from_string (info->id), &value);

        g_value_unset (&value);
    
        set_sensitivity (info, eel_gconf_key_is_writable (info->pref));
    }

    info->loaded = TRUE;
}

static void
save_info (gpointer key, PropertyInfo *info, EphyDialog *dialog)
{
    GValue value = { 0, };

    if (info->pref == NULL || info->apply_type != PT_NORMAL)
    {
        return;
    }

    if (!info->sane_state)
    {
        g_warning ("Not persisting insane state of id[%s]", info->id);
        return;
    }

    set_value_from_info (info, &value);
    set_pref_from_value (info, &value);
    g_value_unset (&value);
}

static void
setup_default_size (EphyDialog *dialog)
{
    if (dialog->priv->has_default_size == FALSE)
    {
        EphyStateWindowFlags flags;

        flags = EPHY_STATE_WINDOW_SAVE_SIZE;

        if (dialog->priv->persist_position)
        {
            flags |= EPHY_STATE_WINDOW_SAVE_POSITION;
        }

        ephy_state_add_window (dialog->priv->dialog,
                       dialog->priv->name, -1, -1,
                       flags);

        dialog->priv->has_default_size = TRUE;
    }
}

static void
dialog_destroy_cb (GtkWidget *widget, EphyDialog *dialog)
{
    g_hash_table_foreach (dialog->priv->props, (GHFunc) save_info, dialog);

    if (dialog->priv->disposing == FALSE)
    {
        g_object_unref (dialog);
    }
}

static void
impl_construct (EphyDialog *dialog,
        const EphyDialogProperty *properties,
        const char *file,
        const char *name,
        const char *domain)
{
    GladeXML *gxml;

    gxml = ephy_glade_widget_new
        (file, name, &(dialog->priv->dialog), dialog, domain);

    if (dialog->priv->name == NULL)
    {
        dialog->priv->name = g_strdup (name);
    }

    if (properties)
    {
        init_props (dialog, properties, gxml);
    }

    g_signal_connect_object (dialog->priv->dialog,
                 "destroy",
                 G_CALLBACK(dialog_destroy_cb),
                 dialog, 0);

    g_object_unref (gxml);
}

static void
impl_show (EphyDialog *dialog)
{
    if (dialog->priv->initialized == FALSE)
    {
        dialog->priv->initialized = TRUE;

        g_hash_table_foreach (dialog->priv->props, (GHFunc) load_info, dialog);
        g_hash_table_foreach (dialog->priv->props, (GHFunc) connect_signals, dialog);
    }

    setup_default_size (dialog);

    if (dialog->priv->parent != NULL)
    {
        /* make the dialog transient again, because it seems to get
         * forgotten after gtk_widget_hide
         */
        gtk_window_set_transient_for (GTK_WINDOW (dialog->priv->dialog),
                          GTK_WINDOW (dialog->priv->parent));
    }

    gtk_window_present (GTK_WINDOW (dialog->priv->dialog));
}

void
ephy_dialog_set_modal (EphyDialog *dialog,
               gboolean is_modal)
{
    dialog->priv->modal = is_modal;

    gtk_window_set_modal (GTK_WINDOW(dialog->priv->dialog), is_modal);
}

void
ephy_dialog_add_enum (EphyDialog *dialog,
              const char *id,
              guint n_items,
              const char **items)
{
    PropertyInfo *info;
    int i = 0;
    GList *l = NULL;

    info = lookup_info (dialog, id);
    g_return_if_fail (info != NULL);

    for (i = 0; i < n_items; i++)
    {
        l = g_list_prepend (l, g_strdup (items[i]));
    }

    info->string_enum = g_list_reverse (l);
}

void
ephy_dialog_set_data_column (EphyDialog *dialog,
                 const char *id,
                 int column)
{
    PropertyInfo *info;

    info = lookup_info (dialog, id);
    g_return_if_fail (info != NULL);

    info->data_col = column;
}

void
ephy_dialog_set_pref (EphyDialog *dialog,
              const char *property_id,
              const char *pref)
{
    PropertyInfo *info;

    info = lookup_info (dialog, property_id);
    g_return_if_fail (info != NULL);

    disconnect_signals (NULL, info, dialog);

    info->loaded = FALSE;
    info->sane_state = FALSE;
    g_free (info->pref);
    info->pref = g_strdup (pref);

    if (dialog->priv->initialized)
    {
        /* dialog is already initialised, so initialise this here */
        load_info (NULL, info, dialog);
        connect_signals (NULL, info, dialog);
    }
}

void
ephy_dialog_set_size_group (EphyDialog *dialog,
                const char *first_id,
                ...)
{
    GtkSizeGroup *size_group;
    va_list vl;

    size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

    va_start (vl, first_id);

    while (first_id != NULL)
    {
        PropertyInfo *info;

        info = lookup_info (dialog, first_id);
        g_return_if_fail (info != NULL);

        g_return_if_fail (info->widget != NULL);

        gtk_size_group_add_widget (size_group, info->widget);

        first_id = va_arg (vl, const char*);
    }

    va_end (vl);
}

void
ephy_dialog_construct (EphyDialog *dialog,
               const EphyDialogProperty *properties,
               const char *file,
               const char *name,
               const char *domain)
{
    EphyDialogClass *klass = EPHY_DIALOG_GET_CLASS (dialog);
    return klass->construct (dialog, properties, file, name, domain);
}

void
ephy_dialog_show (EphyDialog *dialog)
{
    EphyDialogClass *klass = EPHY_DIALOG_GET_CLASS (dialog);
    klass->show (dialog);
}

void
ephy_dialog_hide (EphyDialog *dialog)
{
    g_return_if_fail (EPHY_IS_DIALOG (dialog));
    g_return_if_fail (dialog->priv->dialog != NULL);

    gtk_widget_hide (dialog->priv->dialog);
}

#if 0
static void
run_response_cb (GtkWidget *dialog,
         int response,
         int *result)
{
    *result = response;

    gtk_grab_remove (dialog);
    LOG ("run_response_cb: leaving gtk level %d", gtk_main_level())
    gtk_main_quit();
}
#endif

int
ephy_dialog_run (EphyDialog *dialog)
{
    ephy_dialog_show (dialog);

#if 0
    if (dialog->priv->parent != NULL && dialog->priv->modal == FALSE)
    {
        GtkWindowGroup *group;
        int response = 0;

        group = GTK_WINDOW (dialog->priv->parent)->group;
        if (group == NULL)
        {
            group = gtk_window_group_new ();
            gtk_window_group_add_window (group, GTK_WINDOW (dialog->priv->parent));
            g_object_unref (group);
        }

        gtk_window_group_add_window (group, GTK_WINDOW (dialog->priv->dialog));
        g_signal_connect(dialog->priv->dialog, "response",
                 G_CALLBACK (run_response_cb), &response);
        gtk_grab_add (dialog->priv->dialog);
        LOG ("ephy_dialog_run before main(): level %d", gtk_main_level())
        gtk_main ();
        LOG ("ephy_dialog_run after main(): level %d", gtk_main_level())

        return response;
    }
#endif
    return gtk_dialog_run (GTK_DIALOG (dialog->priv->dialog));
}

GtkWidget *
ephy_dialog_get_control (EphyDialog *dialog,
             const char *property_id)
{
    PropertyInfo *info;

    info = lookup_info (dialog, property_id);
    g_return_val_if_fail (info != NULL, NULL);

    return info->widget;
}

void
ephy_dialog_get_value (EphyDialog *dialog,
               const char *property_id,
               GValue *value)
{
    PropertyInfo *info;

    info = lookup_info (dialog, property_id);
    g_return_if_fail (info != NULL);

    set_value_from_info (info, value);
}

void
ephy_dialog_set_value (EphyDialog *dialog,
               const char *property_id,
               const GValue *value)
{
    PropertyInfo *info;

    info = lookup_info (dialog, property_id);
    g_return_if_fail (info != NULL);

    set_info_from_value (info, value);
}

static void
free_prop_info (PropertyInfo *info)
{
    if (info->string_enum)
    {
        g_list_foreach (info->string_enum, (GFunc)g_free, NULL);
        g_list_free (info->string_enum);
    }

    g_free (info->pref);

    g_free (info);
}

static void
ephy_dialog_init (EphyDialog *dialog)
{
    dialog->priv = EPHY_DIALOG_GET_PRIVATE (dialog);

    dialog->priv->parent = NULL;
    dialog->priv->dialog = NULL;
    dialog->priv->name = NULL;
    dialog->priv->initialized = FALSE;
    dialog->priv->has_default_size = FALSE;
    dialog->priv->disposing = FALSE;
    dialog->priv->persist_position = FALSE;

    dialog->priv->props = g_hash_table_new_full 
        (g_str_hash, g_str_equal, NULL, (GDestroyNotify) free_prop_info);
}

static void
ephy_dialog_dispose (GObject *object)
{
    EphyDialog *dialog = EPHY_DIALOG (object);

    if (dialog->priv->dialog)
    {
        dialog->priv->disposing = TRUE;
        gtk_widget_destroy (dialog->priv->dialog);
        dialog->priv->dialog = NULL;
    }
}

static void
ephy_dialog_finalize (GObject *object)
{
    EphyDialog *dialog = EPHY_DIALOG (object);

    g_hash_table_destroy (dialog->priv->props);

    g_free (dialog->priv->name);

    G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
ephy_dialog_set_parent (EphyDialog *dialog,
            GtkWidget *parent)
{
    dialog->priv->parent = parent;

    g_object_notify (G_OBJECT (dialog), "parent-window");
}

static void
ephy_dialog_set_property (GObject *object,
              guint prop_id,
              const GValue *value,
              GParamSpec *pspec)
{
    EphyDialog *dialog = EPHY_DIALOG (object);

    switch (prop_id)
    {
        case PROP_PARENT_WINDOW:
            ephy_dialog_set_parent (dialog, g_value_get_object (value));
            break;
        case PROP_MODAL:
            ephy_dialog_set_modal (dialog, g_value_get_boolean (value));
            break;
        case PROP_PERSIST_POSITION:
            dialog->priv->persist_position = g_value_get_boolean (value);
            break;
    }
}

static void
ephy_dialog_get_property (GObject *object,
              guint prop_id,
              GValue *value,
              GParamSpec *pspec)
{
    EphyDialog *dialog = EPHY_DIALOG (object);

    switch (prop_id)
    {
        case PROP_PARENT_WINDOW:
            g_value_set_object (value, dialog->priv->parent);
            break;
        case PROP_MODAL:
            g_value_set_boolean (value, dialog->priv->modal);
            break;
        case PROP_PERSIST_POSITION:
            g_value_set_boolean (value, dialog->priv->persist_position);
            break;
    }
}

static void
ephy_dialog_class_init (EphyDialogClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    parent_class = g_type_class_peek_parent (klass);

    object_class->finalize = ephy_dialog_finalize;
    object_class->dispose = ephy_dialog_dispose;
    object_class->set_property = ephy_dialog_set_property;
    object_class->get_property = ephy_dialog_get_property;

    klass->construct = impl_construct;
    klass->show = impl_show;

    signals[CHANGED] =
        g_signal_new ("changed",
                  EPHY_TYPE_DIALOG,
                  G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
                  G_STRUCT_OFFSET (EphyDialogClass, changed),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__POINTER,
                  G_TYPE_NONE,
                  1,
                  G_TYPE_POINTER);

    g_object_class_install_property (object_class,
                     PROP_PARENT_WINDOW,
                     g_param_spec_object ("parent-window",
                                  "Parent window",
                                  "Parent window",
                                  GTK_TYPE_WINDOW,
                                  G_PARAM_READWRITE));

    g_object_class_install_property (object_class,
                     PROP_MODAL,
                     g_param_spec_boolean ("Modal",
                                   "Modal",
                                   "Modal dialog",
                                   FALSE,
                                   G_PARAM_READWRITE));

    g_object_class_install_property (object_class,
                     PROP_PERSIST_POSITION,
                     g_param_spec_boolean ("persist-position",
                                   "Persist position",
                                   "Persist dialog position",
                                   FALSE,
                                   G_PARAM_READWRITE));

    g_type_class_add_private (object_class, sizeof (EphyDialogPrivate));
}

EphyDialog *
ephy_dialog_new (void)
{
    return EPHY_DIALOG (g_object_new (EPHY_TYPE_DIALOG, NULL));
}

EphyDialog *
ephy_dialog_new_with_parent (GtkWidget *parent_window)
{
    g_return_val_if_fail (parent_window != NULL, NULL);

    return EPHY_DIALOG (g_object_new (EPHY_TYPE_DIALOG,
                      "parent-window", parent_window,
                      NULL));
}