aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/e-config.c
blob: 3695069eea1c16af5321ad17930939263fd72512 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
  



                                                                
  



                                                                    
  
                                                                   
                                                                             
  
  



                                                        









                    
                    
                       
 

                     
                       
 
            
 


                                            
                  
                                   
                              








                                       
                      








                                         

                                                                      
 
                                                                




                                        
                      
                               
                      

  







                                              



                        
                            














                                                                  
                                       









                                              
                                     
 
                                                










                                                                              



                                                                                                                              


                           
                                                                            



                                   




                                                                                          























                                                  
                                                                                                                    













                                                              
  

                                                              
  

























                                                                                  
                       
                                                                 
  
                                                              
  

                                 
                                                                    
 
                                                                                         


















                                                                      
  





                                                                     
                                                                                                                                                 

















                                                              
  








                                                                              
                                                      

    
                                                                                                










                                                             









































                                                                                                      















                                                                     
           
                                          
 

                                                   



                                                    








































                                                                                           
           
                                        
 




                                     
 




























                                                                                                    
         

                                                       


           
                                                              
 

                                                          
 

                                                    


           
                                                             
 

                                                           

                                                          

                                                    


           
                                                                                
 

                                               

 

                                                            
 


                                      
 
                                                         
 
                                                                                                                      
 

                                                              
                                                           






                                                                           
                             
                                                     
                                                                   

         
                         


           
                         


                                                                       
                                                                                               
                                                   

                                                     
 
                                                   





                                                                     








                                                                                                                                                    

                                                                                
                                                     

                             
                                                 
 


                                                                 

                                                                           
                                                 









                                                                    
                                                                                                                                                



                                                                        
                                              







                                                                   
                                                                                                                                   




                                          

                                                                          




                                                                   
                                                                                                 




                                                              
                                                                                          




                                                                                                             



                                                                              


                                                






                                                                                                                                    









                                                        
                                                 













                                                                                                          

                                                                                                                  






                                                                                                            













                                                                                                                                                                                                 
                                 
 

                                                  














                                                                                                                    
                         
 







                                                                                

                                                                  
                                                                           


                                                                               







                                                                                                          

                                                                      

                                                         

                                                                                                              



                                                        















                                                                                                                                  
                                        
                                                                                           









                                                                                                       
                                                                                      

                                                               

                                                                                                             






                                           

                                                                                                                 



                                                                    
                                                                                
                                           
                                                                                                     
                                               

                                                  






                                                                                                        

                                                   



                                                                                                             
                                                                                   
                                


                                                        






                                                                                                                  
                                                               
                                                                                                                          









                                                                                  
                                      
                                                                             


                                                                                           

                                                                          
                                                                                    








                                                                                                    


                                                                  
                                                                                               


                                                               

                                                                                       

                                             

                                                                                                                    



                                         



                                                                                        



                                                                    


                                                  
                                                                                                
                                                                                        




                                                                                                     
                                                                                   

                                                            
                                                                                            



                                                               

                                                                                                              
                                         
                         



                              
                                                                   
                                                                




                                                            
                                                                                                                                        


                                                                
                                                          




                                                           
                                                                                                                           

         






                                                                                 






                                                                                         





                                         
  


























                                                                                   
  

                                                                    
                                                                       
        
  















                                                                   
               
 
                                                         































                                                                                                        




                                                                                       




                           
                                                      













                                                              
  

                                                                    


                                                                         


                                                              
  


                                                                        
                                                                           





                                         


                                                                                
                                                                                      


                                                                                     

                                            

                                                                                                                 
 
                                                                                                                         
                

                                                                         
                                                             







                           

                                 
 

                                                 








                                                                                                                    









                                           


                     

                           


        




                                                                   
                                                                      



                                                                        

                                                              
                
                                         






                                    

       
                                                                    
                                                                           















                                                                                      

       
                                                                   
                                                                        















                                                                                      
       
                                      
  

                                                                       
  

                                                              
                                                              







                                                                                        
                                                                
                                     
                     





                     
       
                                      
  
                                                     
  
                                                                  
                                                        
    
                                                              















                                                                                     
       
                                      
  

                                                                    
  


                                                                       
                                                                 

                                
                   

















                                                                                     
       
                                      
  

                                                                         
  


                                                                           
                                                                 

                                
                   

























                                                                                         
  



                                                               
  


                                         
                                                                                                        













                                                            
  














                                                                     
  


                                                                      
                                                                


                         



                                                                        












                              
  



                                                                     
                                             











                                                                            

                                            



                                                      
                                             













                                              
                                  



                                                      
                                            







                                                    
                 


           
                                                     


                                               
                                                               



                                                                                     
                                                    


                                               
                                                              


                                                                                    
               
                                                          


                                               
 

                                               
 






                                                                                                
           
                                               


                                               
                                                                                 
 

                                                   



                                                                                          


                                                                     




















                                                                   
                  
                                                                                                           

                                               
 







                                                 
 
                                                                                                                

                            






                                                                                                         
                                             













                                                                                   
                                                                                                         


                    
                              










                                                                              
                   
 
                                     

                                        
                                                                  








                                                          




                                                                                                  
                                                       




                                                         
                                                               














                                                                                
           




                                                              
                                           







                                                                                    
                                                                

































                                                                                                        
                                                     
 
                                                                                                                         






                                                                                            
  
                                                    
  





                                            
 








                                                                                                           
 








                                                                    
  



                                                                    


                                                                    
 
                                                                                   
 
/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *      Michael Zucchi <notzed@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

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

#include <string.h>
#include <stdlib.h>

#include <gtk/gtk.h>
#include <glib/gi18n.h>

#include "e-config.h"

#include <glib/gi18n.h>

#define d(x)

struct _EConfigFactory {
    struct _EConfigFactory *next, *prev;

    gchar *id;
    EConfigFactoryFunc factory;
    gpointer factory_data;
};

struct _menu_node {
    struct _menu_node *next, *prev;

    GSList *menu;
    EConfigItemsFunc free;
    EConfigItemsFunc abort;
    EConfigItemsFunc commit;
    gpointer data;
};

struct _widget_node {
    struct _widget_node *next, *prev;

    EConfig *config;

    struct _menu_node *context;
    EConfigItem *item;
    GtkWidget *widget; /* widget created by the factory, if any */
    GtkWidget *frame; /* if created by us */

    guint empty:1;      /* set if empty (i.e. hidden) */
};

struct _check_node {
    struct _check_node *next, *prev;

    gchar *pageid;
    EConfigCheckFunc check;
    gpointer data;
};

struct _finish_page_node {
    struct _finish_page_node *next, *prev;

    gchar *pageid;
    gboolean is_finish;
    gint orig_type;
};

struct _EConfigPrivate {
    EDList menus;
    EDList widgets;
    EDList checks;
    EDList finish_pages;
};

static GObjectClass *ep_parent;

static void
ep_init(GObject *o)
{
    EConfig *emp = (EConfig *)o;
    struct _EConfigPrivate *p;

    p = emp->priv = g_malloc0(sizeof(struct _EConfigPrivate));

    e_dlist_init(&p->menus);
    e_dlist_init(&p->widgets);
    e_dlist_init(&p->checks);
    e_dlist_init(&p->finish_pages);
}

static void
ep_finalise(GObject *o)
{
    EConfig *emp = (EConfig *)o;
    struct _EConfigPrivate *p = emp->priv;
    struct _menu_node *mnode;
    struct _widget_node *wn;
    struct _check_node *cn;
    struct _finish_page_node *fp;

    d(printf("finalising EConfig %p\n", o));

    g_free(emp->id);

    while ((mnode = (struct _menu_node *)e_dlist_remhead(&p->menus))) {
        if (mnode->free)
            mnode->free(emp, mnode->menu, mnode->data);

        g_free(mnode);
    }

    while ( (wn = (struct _widget_node *)e_dlist_remhead(&p->widgets)) ) {
        /* disconnect the gtk_widget_destroyed function from the widget */
        if (wn->widget)
            g_signal_handlers_disconnect_matched (wn->widget, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, &wn->widget);

        g_free(wn);
    }

    while ( (cn = (struct _check_node *)e_dlist_remhead(&p->checks)) ) {
        g_free(cn->pageid);
        g_free(cn);
    }

    while ( (fp = (struct _finish_page_node *) e_dlist_remhead (&p->finish_pages)) ) {
        g_free (fp->pageid);
        g_free (fp);
    }

    g_free(p);

    ((GObjectClass *)ep_parent)->finalize(o);
}

static void
ec_target_free(EConfig *ep, EConfigTarget *t)
{
    g_free(t);
    g_object_unref(ep);
}

static void
ec_set_target(EConfig *emp, EConfigTarget *target)
{
    if (emp->target)
        e_config_target_free(emp, target);

    emp->target = target;
}

static void
ep_class_init(GObjectClass *klass)
{
    d(printf("EConfig class init %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type)));

    klass->finalize = ep_finalise;
    ((EConfigClass *)klass)->set_target = ec_set_target;
    ((EConfigClass *)klass)->target_free = ec_target_free;
}

static void
ep_base_init(GObjectClass *klass)
{
    e_dlist_init(&((EConfigClass *)klass)->factories);
}

/**
 * e_config_get_type:
 *
 * Standard GObject method.  Used to subclass for the concrete
 * implementations.
 *
 * Return value: EConfig type.
 **/
GType
e_config_get_type(void)
{
    static GType type = 0;

    if (type == 0) {
        static const GTypeInfo info = {
            sizeof(EConfigClass),
            (GBaseInitFunc)ep_base_init, NULL,
            (GClassInitFunc)ep_class_init, NULL, NULL,
            sizeof(EConfig), 0,
            (GInstanceInitFunc)ep_init
        };
        ep_parent = g_type_class_ref(G_TYPE_OBJECT);
        type = g_type_register_static(G_TYPE_OBJECT, "EConfig", &info, 0);
    }

    return type;
}

/**
 * e_config_construct:
 * @ep: The instance to initialise.
 * @type: The type of configuration manager, @E_CONFIG_BOOK or
 * @E_CONFIG_ASSISTANT.
 * @id: The name of the configuration window this manager drives.
 *
 * Used by implementing classes to initialise base parameters.
 *
 * Return value: @ep is returned.
 **/
EConfig *e_config_construct(EConfig *ep, gint type, const gchar *id)
{
    g_return_val_if_fail (type == E_CONFIG_BOOK || type == E_CONFIG_ASSISTANT, NULL);

    ep->type = type;
    ep->id = g_strdup(id);

    return ep;
}

/**
 * e_config_add_items:
 * @ec: An initialised implementing instance of EConfig.
 * @items: A list of EConfigItem's to add to the configuration manager
 * @ec.
 * @commitfunc: If supplied, called to commit the configuration items
 * to persistent storage.
 * @abortfunc: If supplied, called to abort/undo the storage of these
 * items permanently.
 * @freefunc: If supplied, called to free the item list (and/or items)
 * once they are no longer needed.
 * @data: Data for the callback methods.
 *
 * Add new EConfigItems to the configuration window.  Nothing will be
 * done with them until the widget is built.
 *
 * TODO: perhaps commit and abort should just be signals.
 **/
void
e_config_add_items(EConfig *ec, GSList *items, EConfigItemsFunc commitfunc, EConfigItemsFunc abortfunc, EConfigItemsFunc freefunc, gpointer data)
{
    struct _menu_node *node;

    node = g_malloc(sizeof(*node));
    node->menu = items;
    node->commit = commitfunc;
    node->abort = abortfunc;
    node->free = freefunc;
    node->data = data;
    e_dlist_addtail(&ec->priv->menus, (EDListNode *)node);
}

/**
 * e_config_add_page_check:
 * @ec: Initialised implemeting instance of EConfig.
 * @pageid: pageid to check.
 * @check: checking callback.
 * @data: user-data for the callback.
 *
 * Add a page-checking function callback.  It will be called to validate the
 * data in the given page or pages.  If @pageid is NULL then it will be called
 * to validate every page, or the whole configuration window.
 *
 * In the latter case, the pageid in the callback will be either the
 * specific page being checked, or NULL when the whole config window
 * is being checked.
 *
 * The page check function is used to validate input before allowing
 * the assistant to continue or the notebook to close.
 **/
void
e_config_add_page_check(EConfig *ec, const gchar *pageid, EConfigCheckFunc check, gpointer data)
{
    struct _check_node *cn;

    cn = g_malloc0(sizeof(*cn));
    cn->pageid = g_strdup(pageid);
    cn->check = check;
    cn->data = data;

    e_dlist_addtail(&ec->priv->checks, (EDListNode *)cn);
}

static struct _finish_page_node *
find_page_finish (EConfig *ec, const gchar *pageid)
{
    struct _finish_page_node *fp;

    for (fp = (struct _finish_page_node *) ec->priv->finish_pages.head; fp->next; fp = fp->next) {
        if (g_str_equal (fp->pageid, pageid))
            return fp;
    }

    return NULL;
}

/**
 * e_config_set_page_is_finish:
 * @ec: Initialised implementing instance of EConfig.
 * @pageid: pageid to change the value on.
 * @can_finish: whether the pageid can finish immediately or not.
 *
 * With is_finish set on the pageid the page is treated as the last page in an assistant.
 **/
void
e_config_set_page_is_finish (EConfig *ec, const gchar *pageid, gboolean is_finish)
{
    struct _finish_page_node *fp;

    fp = find_page_finish (ec, pageid);

    if (is_finish) {
        if (!fp) {
            fp = g_malloc0 (sizeof (*fp));
            fp->pageid = g_strdup (pageid);
            e_dlist_addtail (&ec->priv->finish_pages, (EDListNode *)fp);
        }

        fp->is_finish = TRUE;
    } else {
        if (fp)
            fp->is_finish = FALSE;
    }
}

static void
ec_add_static_items(EConfig *ec)
{
    struct _EConfigFactory *f;
    EConfigClass *klass = (EConfigClass *)G_OBJECT_GET_CLASS(ec);

    f = (struct _EConfigFactory *)klass->factories.head;
    while (f->next) {
        if (f->id == NULL
            || !strcmp(f->id, ec->id)) {
            f->factory(ec, f->factory_data);
        }
        f = f->next;
    }
}

static gint
ep_cmp(gconstpointer ap, gconstpointer bp)
{
    struct _widget_node *a = *((gpointer *)ap);
    struct _widget_node *b = *((gpointer *)bp);

    return strcmp(a->item->path, b->item->path);
}

static struct _widget_node *
ec_assistant_find_page (EConfig *ec, GtkWidget *page, gint *page_index)
{
    struct _widget_node *wn;

    g_return_val_if_fail (ec != NULL, NULL);
    g_return_val_if_fail (GTK_IS_ASSISTANT (ec->widget), NULL);
    g_return_val_if_fail (page != NULL, NULL);

    for (wn = (struct _widget_node *)ec->priv->widgets.head; wn->next; wn = wn->next) {
        if (wn->frame == page
            && (wn->item->type == E_CONFIG_PAGE
            || wn->item->type == E_CONFIG_PAGE_START
            || wn->item->type == E_CONFIG_PAGE_FINISH))
                break;
    }

    if (wn->frame != page)
        wn = NULL;

    if (page_index) {
        if (wn) {
            GtkAssistant *assistant = GTK_ASSISTANT (ec->widget);
            gint index, count = gtk_assistant_get_n_pages (assistant);

            for (index = 0; index < count; index++) {
                if (gtk_assistant_get_nth_page (assistant, index) == page)
                    break;
            }

            if (index == count)
                index = -1;
            *page_index = index;
        } else {
            *page_index = -1;
        }
    }

    return wn;
}

static void
ec_assistant_check_current (EConfig *ec)
{
    struct _widget_node *wn;
    struct _finish_page_node *fp;
    GtkAssistant *assistant;
    GtkWidget *page;
    gint page_no;

    g_return_if_fail (GTK_IS_ASSISTANT (ec->widget));

    assistant = GTK_ASSISTANT (ec->widget);
    page_no = gtk_assistant_get_current_page (assistant);

    /* no page selected yet */
    if (page_no == -1)
        return;

    page = gtk_assistant_get_nth_page (assistant, page_no);
    g_return_if_fail (page != NULL);

    wn = ec_assistant_find_page (ec, page, NULL);
    g_return_if_fail (wn != NULL);

    /* this should come first, as the check function can change the finish state of the page */
    gtk_assistant_set_page_complete (assistant, page, e_config_page_check (ec, wn->item->path));

    fp = find_page_finish (ec, wn->item->path);
    if (fp) {
        GtkAssistantPageType pt = gtk_assistant_get_page_type (assistant, page);

        if (fp->is_finish && pt != GTK_ASSISTANT_PAGE_CONFIRM) {
            if (fp->orig_type == GTK_ASSISTANT_PAGE_CONTENT)
                fp->orig_type = pt;
            gtk_assistant_set_page_type (assistant, page, GTK_ASSISTANT_PAGE_CONFIRM);
        } else if (!fp->is_finish && pt != fp->orig_type) {
            gtk_assistant_set_page_type (assistant, page, fp->orig_type);
        }
    }

    gtk_assistant_update_buttons_state (assistant);
}

static void
ec_assistant_cancel (GtkAssistant *assistant, EConfig *config)
{
    d(printf("finishing assistant, calling abort\n"));
    e_config_abort (config);

    if (config->window)
        gtk_widget_destroy (config->window);
}

static void
ec_assistant_apply (GtkAssistant *assistant, EConfig *config)
{
    d(printf("finishing assistant, calling commit\n"));
    e_config_commit (config);

    /* TODO: allow the commit to fail?  Do we care? */
    if (config->window)
        gtk_widget_destroy (config->window);
}

static void
ec_assistant_prepare (GtkAssistant *assistant, GtkWidget *page, EConfig *config)
{
    d(printf("prepare page '%p'\n", page));
    ec_assistant_check_current (config);
}

static gint
ec_assistant_forward (gint current_page, gpointer user_data)
{
    EConfig *ec = user_data;
    struct _widget_node *wn;
    gint next_page = current_page;

    d(printf("next page from '%d'\n", current_page));

    wn = ec_assistant_find_page (ec, gtk_assistant_get_nth_page (GTK_ASSISTANT (ec->widget), current_page), NULL);

    if (wn && wn->next) {
        for (wn = wn->next; wn->next; wn = wn->next) {
            if (!wn->empty && wn->frame != NULL
                && (wn->item->type == E_CONFIG_PAGE
                || wn->item->type == E_CONFIG_PAGE_START
                || wn->item->type == E_CONFIG_PAGE_FINISH))
                break;
        }
    }

    if (wn && wn->next) {
        d(printf(" is %s\n",wn->item->path));
        ec_assistant_find_page (ec, wn->frame, &next_page);
    }

    return next_page;
}

static void
ec_rebuild (EConfig *emp)
{
    struct _EConfigPrivate *p = emp->priv;
    struct _widget_node *wn, *sectionnode = NULL, *pagenode = NULL;
    GtkWidget *book = NULL, *page = NULL, *section = NULL, *root = NULL, *assistant = NULL;
    gint pageno = 0, sectionno = 0, itemno = 0;
    struct _widget_node *last_active_page = NULL;
    gboolean is_assistant;

    d(printf("target changed, rebuilding:\n"));

    /* TODO: This code is pretty complex, and will probably just
     * become more complex with time.  It could possibly be split
     * into the two base types, but there would be a lot of code
     * duplication */

    /* because rebuild destroys pages, and destroying active page causes crashes */
    is_assistant = emp->widget && GTK_IS_ASSISTANT (emp->widget);
    if (is_assistant) {
        gint page_index = gtk_assistant_get_current_page (GTK_ASSISTANT (emp->widget));
        if (page_index != -1)
            last_active_page = ec_assistant_find_page (emp, gtk_assistant_get_nth_page (GTK_ASSISTANT (emp->widget), page_index), NULL);
        gtk_assistant_set_current_page (GTK_ASSISTANT (emp->widget), 0);
    }

    for (wn = (struct _widget_node *)p->widgets.head;wn->next;wn=wn->next) {
        struct _EConfigItem *item = wn->item;
        const gchar *translated_label = NULL;
        GtkWidget *w;

        d(printf(" '%s'\n", item->path));

        if (item->label != NULL)
            translated_label = gettext (item->label);

        /* If the last section doesn't contain anything, hide it */
        if (sectionnode != NULL
            && sectionnode->frame != NULL
            && (item->type == E_CONFIG_PAGE_START
            || item->type == E_CONFIG_PAGE_FINISH
            || item->type == E_CONFIG_PAGE
            || item->type == E_CONFIG_SECTION
            || item->type == E_CONFIG_SECTION_TABLE)) {
            if ( (sectionnode->empty = itemno == 0) ) {
                gtk_widget_hide(sectionnode->frame);
                sectionno--;
            } else
                gtk_widget_show(sectionnode->frame);
            d(printf("%s section '%s' [sections=%d]\n", sectionnode->empty?"hiding":"showing", sectionnode->item->path, sectionno));
        }

        /* If the last page doesn't contain anything, hide it */
        if (pagenode != NULL
            && pagenode->frame != NULL
            && (item->type == E_CONFIG_PAGE_START
            || item->type == E_CONFIG_PAGE_FINISH
            || item->type == E_CONFIG_PAGE)) {
            if ( (pagenode->empty = sectionno == 0) ) {
                gtk_widget_hide(pagenode->frame);
                pageno--;
            } else
                gtk_widget_show(pagenode->frame);
            d(printf("%s page '%s' [section=%d]\n", pagenode->empty?"hiding":"showing", pagenode->item->path, pageno));
        }

        /* Now process the item */
        switch (item->type) {
        case E_CONFIG_BOOK:
        case E_CONFIG_ASSISTANT:
            /* Only one of BOOK or ASSISTANT may be define, it
               is used by the defining code to mark the
               type of the config window.  It is
               cross-checked with the code's defined
               type. */
            if (root != NULL) {
                g_warning("EConfig book/assistant redefined at: %s", item->path);
                break;
            }

            if (wn->widget == NULL) {
                if (item->type != emp->type) {
                    g_warning("EConfig book/assistant type mismatch");
                    break;
                }
                if (item->factory) {
                    root = item->factory(emp, item, NULL, wn->widget, wn->context->data);
                } else if (item->type == E_CONFIG_BOOK) {
                    root = gtk_notebook_new();
                    gtk_widget_show (root);
                } else if (item->type == E_CONFIG_ASSISTANT) {
                    root = gtk_assistant_new ();
                } else
                    abort();

                if (item->type == E_CONFIG_ASSISTANT) {
                    g_signal_connect (root, "cancel", G_CALLBACK (ec_assistant_cancel), emp);
                    g_signal_connect (root, "close", G_CALLBACK (ec_assistant_cancel), emp);
                    g_signal_connect (root, "apply", G_CALLBACK (ec_assistant_apply), emp);
                    g_signal_connect (root, "prepare", G_CALLBACK (ec_assistant_prepare), emp);
                    gtk_assistant_set_forward_page_func (GTK_ASSISTANT (root), ec_assistant_forward, emp, NULL);
                }

                emp->widget = root;
                wn->widget = root;
            } else {
                root = wn->widget;
            }

            if (item->type == E_CONFIG_BOOK)
                book = root;
            else
                assistant = root;

            page = NULL;
            pagenode = NULL;
            section = NULL;
            sectionnode = NULL;
            pageno = 0;
            sectionno = 0;
            break;
        case E_CONFIG_PAGE_START:
        case E_CONFIG_PAGE_FINISH:
            if (root == NULL) {
                g_warning("EConfig page defined before container widget: %s", item->path);
                break;
            }
            if (emp->type != E_CONFIG_ASSISTANT) {
                g_warning("EConfig assistant start/finish pages can't be used on E_CONFIG_BOOKs");
                break;
            }

            if (wn->widget == NULL) {
                if (item->factory) {
                    page = item->factory(emp, item, root, wn->frame, wn->context->data);
                } else {
                    page = gtk_vbox_new (FALSE, 0);
                    gtk_container_set_border_width (GTK_CONTAINER (page), 12);
                    if (pagenode) {
                        /* put after */
                        gint index = -1;
                        ec_assistant_find_page (emp, pagenode->frame, &index);
                        gtk_assistant_insert_page (GTK_ASSISTANT (assistant), page, index + 1);
                    } else {
                        gtk_assistant_prepend_page (GTK_ASSISTANT (assistant), page);
                    }

                    gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), page, item->type == E_CONFIG_PAGE_START ? GTK_ASSISTANT_PAGE_INTRO : GTK_ASSISTANT_PAGE_CONFIRM);
                    gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), page, translated_label);
                    gtk_widget_show_all (page);
                }

                wn->frame = page;
                wn->widget = page;

                if (page) {
                    const gchar *empty_xpm_img[] = {
                        "48 1 2 1",
                        "   c None",
                        ".  c #FFFFFF",
                        "                                                "};

                    /* left side place with a blue background on a start and finish page */
                    GdkPixbuf *spacer = gdk_pixbuf_new_from_xpm_data (empty_xpm_img);

                    gtk_assistant_set_page_side_image (GTK_ASSISTANT (assistant), page, spacer);

                    g_object_unref (spacer);
                }
            }

            pageno++;
            page = NULL;
            pagenode = wn; /* need this for previous page linking */
            section = NULL;
            sectionnode = NULL;
            sectionno = 1; /* never want to hide these */
            break;
        case E_CONFIG_PAGE: {
            /* CONFIG_PAGEs depend on the config type.
               E_CONFIG_BOOK:
                The page is a VBox, stored in the notebook.
               E_CONFIG_ASSISTANT
                The page is a VBox, stored in the GtkAssistant,
                any sections automatically added inside it. */
            sectionno = 0;
            if (root == NULL) {
                g_warning("EConfig page defined before container widget: %s", item->path);
                break;
            }

            if (item->factory) {
                page = item->factory(emp, item, root, wn->frame, wn->context->data);
                if (emp->type == E_CONFIG_ASSISTANT) {
                    wn->frame = page;
                } else {
                    wn->frame = page;
                    if (page)
                        gtk_notebook_reorder_child((GtkNotebook *)book, page, pageno);
                }
                if (page)
                    sectionno = 1;
            } else if (wn->widget == NULL) {
                if (emp->type == E_CONFIG_ASSISTANT) {
                    page = gtk_vbox_new (FALSE, 0);
                    gtk_container_set_border_width (GTK_CONTAINER (page), 12);
                    if (pagenode) {
                        /* put after */
                        gint index = -1;
                        ec_assistant_find_page (emp, pagenode->frame, &index);
                        gtk_assistant_insert_page (GTK_ASSISTANT (assistant), page, index + 1);
                    } else {
                        gtk_assistant_prepend_page (GTK_ASSISTANT (assistant), page);
                    }
                    gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), page, GTK_ASSISTANT_PAGE_CONTENT);
                    gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), page, translated_label);
                    gtk_widget_show_all (page);

                    wn->frame = page;
                } else {
                    w = gtk_label_new_with_mnemonic (translated_label);
                    gtk_widget_show(w);
                    page = gtk_vbox_new(FALSE, 12);
                    gtk_container_set_border_width((GtkContainer *)page, 12);
                    gtk_widget_show(page);
                    gtk_notebook_insert_page((GtkNotebook *)book, page, w, pageno);
                    wn->frame = page;
                }
            } else
                page = wn->widget;

            d(printf("page %d:%s widget %p\n", pageno, item->path, page));

            if (wn->widget && wn->widget != page) {
                d(printf("destroy old widget for page '%s' (%p)\n", item->path, wn->widget));
                gtk_widget_destroy (wn->widget);
            }

            pageno++;
            pagenode = wn;
            section = NULL;
            sectionnode = NULL;
            wn->widget = page;
            if (page)
                g_signal_connect(page, "destroy", G_CALLBACK(gtk_widget_destroyed), &wn->widget);
            break; }
        case E_CONFIG_SECTION:
        case E_CONFIG_SECTION_TABLE:
            /* The section factory is always called with
               the parent vbox object.  Even for assistant pages. */
            if (page == NULL) {
                /*g_warning("EConfig section '%s' has no parent page", item->path);*/
                section = NULL;
                wn->widget = NULL;
                wn->frame = NULL;
                goto nopage;
            }

            itemno = 0;
            if (item->factory) {
                section = item->factory(emp, item, page, wn->widget, wn->context->data);
                wn->frame = section;
                if (section)
                    itemno = 1;

                if (section
                    && ((item->type == E_CONFIG_SECTION && !GTK_IS_BOX(section))
                    || (item->type == E_CONFIG_SECTION_TABLE && !GTK_IS_TABLE(section))))
                    g_warning("EConfig section type is wrong");
            } else {
                GtkWidget *frame;
                GtkWidget *label = NULL;

                if (wn->frame) {
                    d(printf("Item %s, clearing generated section widget\n", wn->item->path));
                    gtk_widget_destroy(wn->frame);
                    wn->widget = NULL;
                    wn->frame = NULL;
                }

                if (translated_label != NULL) {
                    gchar *txt = g_strdup_printf("<span weight=\"bold\">%s</span>", translated_label);

                    label = g_object_new(gtk_label_get_type(),
                                 "label", txt,
                                 "use_markup", TRUE,
                                 "xalign", 0.0, NULL);
                    g_free(txt);
                }

                if (item->type == E_CONFIG_SECTION)
                    section = gtk_vbox_new(FALSE, 6);
                else {
                    section = gtk_table_new(1, 1, FALSE);
                    gtk_table_set_col_spacings((GtkTable *)section, 6);
                    gtk_table_set_row_spacings((GtkTable *)section, 6);
                }

                frame = g_object_new(gtk_frame_get_type(),
                             "shadow_type", GTK_SHADOW_NONE,
                             "label_widget", label,
                             "child", g_object_new(gtk_alignment_get_type(),
                                       "left_padding", 12,
                                       "top_padding", 6,
                                       "child", section, NULL),
                             NULL);
                gtk_widget_show_all(frame);
                gtk_box_pack_start((GtkBox *)page, frame, FALSE, FALSE, 0);
                wn->frame = frame;
            }
        nopage:
            if (wn->widget && wn->widget != section) {
                d(printf("destroy old widget for section '%s'\n", item->path));
                gtk_widget_destroy(wn->widget);
            }

            d(printf("Item %s, setting section widget\n", wn->item->path));

            sectionno++;
            wn->widget = section;
            if (section)
                g_signal_connect(section, "destroy", G_CALLBACK(gtk_widget_destroyed), &wn->widget);
            sectionnode = wn;
            break;
        case E_CONFIG_ITEM:
        case E_CONFIG_ITEM_TABLE:
            /* generated sections never retain their widgets on a rebuild */
            if (sectionnode->item->factory == NULL)
                wn->widget = NULL;

            /* ITEMs are called with the section parent.
               The type depends on the section type,
               either a GtkTable, or a GtkVBox */
            w = NULL;
            if (section == NULL) {
                wn->widget = NULL;
                wn->frame = NULL;
                g_warning("EConfig item has no parent section: %s", item->path);
            } else if ((item->type == E_CONFIG_ITEM && !GTK_IS_BOX(section))
                 || (item->type == E_CONFIG_ITEM_TABLE && !GTK_IS_TABLE(section)))
                g_warning("EConfig item parent type is incorrect: %s", item->path);
            else if (item->factory)
                w = item->factory(emp, item, section, wn->widget, wn->context->data);

            d(printf("item %d:%s widget %p\n", itemno, item->path, w));

            if (wn->widget && wn->widget != w) {
                d(printf("destroy old widget for item '%s'\n", item->path));
                gtk_widget_destroy(wn->widget);
            }

            wn->widget = w;
            if (w) {
                g_signal_connect(w, "destroy", G_CALLBACK(gtk_widget_destroyed), &wn->widget);
                itemno++;
            }
            break;
        }
    }

    /* If the last section doesn't contain anything, hide it */
    if (sectionnode != NULL && sectionnode->frame != NULL) {
        if ( (sectionnode->empty = itemno == 0) ) {
            gtk_widget_hide(sectionnode->frame);
            sectionno--;
        } else
            gtk_widget_show(sectionnode->frame);
        d(printf("%s section '%s' [sections=%d]\n", sectionnode->empty?"hiding":"showing", sectionnode->item->path, sectionno));
    }

    /* If the last page doesn't contain anything, hide it */
    if (pagenode != NULL && pagenode->frame != NULL) {
        if ( (pagenode->empty = sectionno == 0) ) {
            gtk_widget_hide(pagenode->frame);
            pageno--;
        } else
            gtk_widget_show(pagenode->frame);
        d(printf("%s page '%s' [section=%d]\n", pagenode->empty?"hiding":"showing", pagenode->item->path, pageno));
    }

    if (book) {
        /* make this depend on flags?? */
        if (gtk_notebook_get_n_pages((GtkNotebook *)book) == 1) {
            gtk_notebook_set_show_tabs((GtkNotebook *)book, FALSE);
            gtk_notebook_set_show_border((GtkNotebook *)book, FALSE);
        }
    }

    if (is_assistant && last_active_page) {
        gint page_index = -1;

        ec_assistant_find_page (emp, last_active_page->frame, &page_index);
        gtk_assistant_set_current_page (GTK_ASSISTANT (emp->widget), page_index);
    }
}

/**
 * e_config_set_target:
 * @emp: An initialised EConfig.
 * @target: A target allocated from @emp.
 *
 * Sets the target object for the config window.  Generally the target
 * is set only once, and will supply its own "changed" signal which
 * can be used to drive the modal.  This is a virtual method so that
 * the implementing class can connect to the changed signal and
 * initiate a e_config_target_changed() call where appropriate.
 **/
void
e_config_set_target(EConfig *emp, EConfigTarget *target)
{
    if (emp->target != target)
        ((EConfigClass *)G_OBJECT_GET_CLASS(emp))->set_target(emp, target);
}

static void
ec_widget_destroy(GtkWidget *w, EConfig *ec)
{
    if (ec->target) {
        e_config_target_free(ec, ec->target);
        ec->target = NULL;
    }

    g_object_unref(ec);
}

/**
 * e_config_create_widget:
 * @emp: An initialised EConfig object.
 *
 * Create the widget described by @emp.  Only the core widget
 * appropriate for the given type is created, i.e. a GtkNotebook for
 * the E_CONFIG_BOOK type and a GtkAssistant for the E_CONFIG_ASSISTANT
 * type.
 *
 * This object will be self-driving, but will not close itself once
 * complete.
 *
 * Unless reffed otherwise, the management object @emp will be
 * finalised when the widget is.
 *
 * Return value: The widget, also available in @emp.widget
 **/
GtkWidget *
e_config_create_widget(EConfig *emp)
{
    struct _EConfigPrivate *p = emp->priv;
    struct _menu_node *mnode;
    GPtrArray *items = g_ptr_array_new();
    GSList *l;
    /*char *domain = NULL;*/
    gint i;

    g_return_val_if_fail (emp->target != NULL, NULL);

    ec_add_static_items(emp);

    /* FIXME: need to override old ones with new names */
    for (mnode = (struct _menu_node *)p->menus.head;mnode->next;mnode=mnode->next)
        for (l=mnode->menu; l; l = l->next) {
            struct _EConfigItem *item = l->data;
            struct _widget_node *wn = g_malloc0(sizeof(*wn));

            wn->item = item;
            wn->context = mnode;
            wn->config = emp;
            g_ptr_array_add(items, wn);
        }

    qsort(items->pdata, items->len, sizeof(items->pdata[0]), ep_cmp);

    for (i=0;i<items->len;i++) {
        struct _widget_node *wn = items->pdata[i];

        e_dlist_addtail(&p->widgets, (EDListNode *)wn);
    }

    g_ptr_array_free(items, TRUE);
    ec_rebuild(emp);

    /* auto-unref it */
    g_signal_connect(emp->widget, "destroy", G_CALLBACK(ec_widget_destroy), emp);

    /* FIXME: for some reason ec_rebuild puts the widget on page 1, this is just to override that */
    if (emp->type == E_CONFIG_BOOK)
        gtk_notebook_set_current_page((GtkNotebook *)emp->widget, 0);
    else {
        gtk_assistant_set_current_page (GTK_ASSISTANT (emp->widget), 0);
        gtk_window_set_position (GTK_WINDOW (emp->widget), GTK_WIN_POS_CENTER);
        gtk_widget_show (emp->widget);
    }

    return emp->widget;
}

static void
ec_dialog_response(GtkWidget *d, gint id, EConfig *ec)
{
    if (id == GTK_RESPONSE_OK)
        e_config_commit(ec);
    else
        e_config_abort(ec);

    gtk_widget_destroy(d);
}

/**
 * e_config_create_window:
 * @emp: Initialised and configured EMConfig derived instance.
 * @parent: Parent window or NULL.
 * @title: Title of window or dialog.
 *
 * Create a managed GtkWindow object from @emp.  This window will be
 * fully driven by the EConfig @emp.  If @emp.type is
 * @E_CONFIG_ASSISTANT, then this will be a toplevel GtkWindow containing
 * a GtkAssistant.  If it is @E_CONFIG_BOOK then it will be a GtkDialog
 * containing a Notebook.
 *
 * Unless reffed otherwise, the management object @emp will be
 * finalised when the widget is.
 *
 * Return value: The window widget.  This is also stored in @emp.window.
 **/
GtkWidget *
e_config_create_window(EConfig *emp, GtkWindow *parent, const gchar *title)
{
    GtkWidget *w;

    e_config_create_widget(emp);

    if (emp->type == E_CONFIG_BOOK) {
        w = gtk_dialog_new_with_buttons(title, parent,
                        GTK_DIALOG_DESTROY_WITH_PARENT |
                        GTK_DIALOG_NO_SEPARATOR,
                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                        GTK_STOCK_OK, GTK_RESPONSE_OK,
                        NULL);
        g_signal_connect(w, "response", G_CALLBACK(ec_dialog_response), emp);

        gtk_widget_ensure_style (w);
        gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (w))), 0);
        gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (w))), 12);

        gtk_box_pack_start((GtkBox *)gtk_dialog_get_content_area (((GtkDialog *)w)), emp->widget, TRUE, TRUE, 0);
    } else {
        /* response is handled directly by the assistant stuff */
        w = emp->widget;
        gtk_window_set_title ((GtkWindow *)w, title);
    }

    emp->window = w;
    gtk_widget_show(w);

    return w;
}

static void
ec_call_page_check (EConfig *emp)
{
    if (emp->type == E_CONFIG_ASSISTANT) {
        ec_assistant_check_current (emp);
    } else {
        if (emp->window) {
            if (e_config_page_check(emp, NULL)) {
                gtk_dialog_set_response_sensitive((GtkDialog *)emp->window, GTK_RESPONSE_OK, TRUE);
            } else {
                gtk_dialog_set_response_sensitive((GtkDialog *)emp->window, GTK_RESPONSE_OK, FALSE);
            }
        }
    }
}

static gboolean
ec_idle_handler_for_rebuild (gpointer data)
{
    EConfig *emp = (EConfig*) data;

    ec_rebuild (emp);
    ec_call_page_check (emp);

    return FALSE;
}

/**
 * e_config_target_changed:
 * @emp:
 * @how:
 *
 * Indicate that the target has changed.  This may be called by the
 * self-aware target itself, or by the driving code.  If @how is
 * %E_CONFIG_TARGET_CHANGED_REBUILD, then the entire configuration
 * widget may be recreated based on the changed target.
 *
 * This is used to sensitise Assistant next/back buttons and the Apply
 * button for the Notebook mode.
 **/
void e_config_target_changed(EConfig *emp, e_config_target_change_t how)
{
    if (how == E_CONFIG_TARGET_CHANGED_REBUILD) {
        g_idle_add (ec_idle_handler_for_rebuild, emp);
    } else {
        ec_call_page_check (emp);
    }

    /* virtual method/signal? */
}

/**
 * e_config_abort:
 * @ec:
 *
 * Signify that the stateful configuration changes must be discarded
 * to all listeners.  This is used by self-driven assistant or notebook, or
 * may be used by code using the widget directly.
 **/
void e_config_abort(EConfig *ec)
{
    struct _EConfigPrivate *p = ec->priv;
    struct _menu_node *mnode;

    /* TODO: should these just be signals? */

    for (mnode = (struct _menu_node *)p->menus.head;mnode->next;mnode=mnode->next)
        if (mnode->abort)
            mnode->abort(ec, mnode->menu, mnode->data);
}

/**
 * e_config_commit:
 * @ec:
 *
 * Signify that the stateful configuration changes should be saved.
 * This is used by the self-driven assistant or notebook, or may be used
 * by code driving the widget directly.
 **/
void e_config_commit(EConfig *ec)
{
    struct _EConfigPrivate *p = ec->priv;
    struct _menu_node *mnode;

    /* TODO: should these just be signals? */

    for (mnode = (struct _menu_node *)p->menus.head;mnode->next;mnode=mnode->next)
        if (mnode->commit)
            mnode->commit(ec, mnode->menu, mnode->data);
}

/**
 * e_config_page_check:
 * @ec:
 * @pageid: The path of the page item.
 *
 * Check that a given page is complete.  If @pageid is NULL, then check
 * the whole config.  No check is made that the page actually exists.
 *
 * Return value: FALSE if the data is inconsistent/incomplete.
 **/
gboolean e_config_page_check(EConfig *ec, const gchar *pageid)
{
    struct _EConfigPrivate *p = ec->priv;
    struct _check_node *mnode;

    for (mnode = (struct _check_node *)p->checks.head;mnode->next;mnode=mnode->next)
        if ((pageid == NULL
             || mnode->pageid == NULL
             || strcmp(mnode->pageid, pageid) == 0)
            && !mnode->check(ec, pageid, mnode->data)) {
            return FALSE;
            }

    return TRUE;
}

/**
 * e_config_page_get:
 * @ec:
 * @pageid: The path of the page item.
 *
 * Retrieve the page widget corresponding to @pageid.
 *
 * Return value: The page widget.  It will be the root GtkNotebook
 * container or the GtkVBox object inside the assistant.
 **/
GtkWidget *e_config_page_get(EConfig *ec, const gchar *pageid)
{
    struct _widget_node *wn;

    for (wn = (struct _widget_node *)ec->priv->widgets.head;wn->next;wn=wn->next)
        if (!wn->empty
            && (wn->item->type == E_CONFIG_PAGE
            || wn->item->type == E_CONFIG_PAGE_START
            || wn->item->type == E_CONFIG_PAGE_FINISH)
            && !strcmp(wn->item->path, pageid))
            return wn->frame;

    return NULL;
}

/**
 * e_config_page_next:
 * @ec:
 * @pageid: The path of the page item.
 *
 * Find the path of the next visible page after @pageid.  If @pageid
 * is NULL then find the first visible page.
 *
 * Return value: The path of the next page, or @NULL if @pageid was the
 * last configured and visible page.
 **/
const gchar *e_config_page_next(EConfig *ec, const gchar *pageid)
{
    struct _widget_node *wn;
    gint found;

    found = pageid == NULL ? 1:0;
    for (wn = (struct _widget_node *)ec->priv->widgets.head;wn->next;wn=wn->next)
        if (!wn->empty
            && (wn->item->type == E_CONFIG_PAGE
            || wn->item->type == E_CONFIG_PAGE_START
            || wn->item->type == E_CONFIG_PAGE_FINISH)) {
            if (found)
                return wn->item->path;
            else if (strcmp(wn->item->path, pageid) == 0)
                found = 1;
        }

    return NULL;
}

/**
 * e_config_page_next:
 * @ec:
 * @pageid: The path of the page item.
 *
 * Find the path of the previous visible page before @pageid.  If @pageid
 * is NULL then find the last visible page.
 *
 * Return value: The path of the previous page, or @NULL if @pageid was the
 * first configured and visible page.
 **/
const gchar *e_config_page_prev(EConfig *ec, const gchar *pageid)
{
    struct _widget_node *wn;
    gint found;

    found = pageid == NULL ? 1:0;
    for (wn = (struct _widget_node *)ec->priv->widgets.tailpred;wn->prev;wn=wn->prev)
        if (!wn->empty
            && (wn->item->type == E_CONFIG_PAGE
            || wn->item->type == E_CONFIG_PAGE_START
            || wn->item->type == E_CONFIG_PAGE_FINISH)) {
            if (found)
                return wn->item->path;
            else if (strcmp(wn->item->path, pageid) == 0)
                found = 1;
        }

    return NULL;
}

/* ********************************************************************** */

/**
 * e_config_class_add_factory:
 * @klass: Implementing class pointer.
 * @id: The name of the configuration window you're interested in.
 * This may be NULL to be called for all windows.
 * @func: An EConfigFactoryFunc to call when the window @id is being
 * created.
 * @data: Callback data.
 *
 * Add a config factory which will be called to add_items() any
 * extra items's if wants to, to the current Config window.
 *
 * TODO: Make the id a pattern?
 *
 * Return value: A handle to the factory.
 **/
EConfigFactory *
e_config_class_add_factory(EConfigClass *klass, const gchar *id, EConfigFactoryFunc func, gpointer data)
{
    struct _EConfigFactory *f = g_malloc0(sizeof(*f));

    f->id = g_strdup(id);
    f->factory = func;
    f->factory_data = data;
    e_dlist_addtail(&klass->factories, (EDListNode *)f);

    return f;
}

/**
 * e_config_class_remove_factory:
 * @f: Handle from :class_add_factory() call.
 *
 * Remove a config factory.  The handle @f may only be removed once.
 **/
void
e_config_class_remove_factory(EConfigClass *klass, EConfigFactory *f)
{
    e_dlist_remove((EDListNode *)f);
    g_free(f->id);
    g_free(f);
}

/**
 * e_config_target_new:
 * @ep: Parent EConfig object.
 * @type: type, up to implementor
 * @size: Size of object to allocate.
 *
 * Allocate a new config target suitable for this class.  Implementing
 * classes will define the actual content of the target.
 **/
gpointer e_config_target_new(EConfig *ep, gint type, gsize size)
{
    EConfigTarget *t;

    if (size < sizeof(EConfigTarget)) {
        g_warning ("Size is less than size of EConfigTarget\n");
        size = sizeof (EConfigTarget);
    }

    t = g_malloc0(size);
    t->config = ep;
    g_object_ref(ep);
    t->type = type;

    return t;
}

/**
 * e_config_target_free:
 * @ep: Parent EConfig object.
 * @o: The target to fre.
 *
 * Free a target.  The implementing class can override this method to
 * free custom targets.
 **/
void
e_config_target_free(EConfig *ep, gpointer o)
{
    EConfigTarget *t = o;

    ((EConfigClass *)G_OBJECT_GET_CLASS(ep))->target_free(ep, t);
}

/* ********************************************************************** */

/* Config menu plugin handler */

/*
<e-plugin
  class="org.gnome.mail.plugin.config:1.0"
  id="org.gnome.mail.plugin.config.item:1.0"
  type="shlib"
  location="/opt/gnome2/lib/camel/1.0/libcamelimap.so"
  name="imap"
  description="IMAP4 and IMAP4v1 mail store">
  <hook class="org.gnome.mail.configMenu:1.0"
        handler="HandleConfig">
  <menu id="any" target="select">
   <item
    type="item|toggle|radio|image|submenu|bar"
    active
    path="foo/bar"
    label="label"
    icon="foo"
    activate="ep_view_emacs"/>
  </menu>
  </extension>

*/

static gpointer emph_parent_class;
#define emph ((EConfigHook *)eph)

static const EPluginHookTargetKey ech_item_types[] = {
    { "book", E_CONFIG_BOOK },
    { "assistant", E_CONFIG_ASSISTANT },

    { "page", E_CONFIG_PAGE },
    { "page_start", E_CONFIG_PAGE_START },
    { "page_finish", E_CONFIG_PAGE_FINISH },
    { "section", E_CONFIG_SECTION },
    { "section_table", E_CONFIG_SECTION_TABLE },
    { "item", E_CONFIG_ITEM },
    { "item_table", E_CONFIG_ITEM_TABLE },
    { NULL },
};

static void
ech_commit(EConfig *ec, GSList *items, gpointer data)
{
    struct _EConfigHookGroup *group = data;

    if (group->commit && group->hook->hook.plugin->enabled)
        e_plugin_invoke(group->hook->hook.plugin, group->commit, ec->target);
}

static void
ech_abort(EConfig *ec, GSList *items, gpointer data)
{
    struct _EConfigHookGroup *group = data;

    if (group->abort && group->hook->hook.plugin->enabled)
        e_plugin_invoke(group->hook->hook.plugin, group->abort, ec->target);
}

static gboolean
ech_check(EConfig *ec, const gchar *pageid, gpointer data)
{
    struct _EConfigHookGroup *group = data;
    EConfigHookPageCheckData hdata;

    if (!group->hook->hook.plugin->enabled)
        return TRUE;

    hdata.config = ec;
    hdata.target = ec->target;
    hdata.pageid = pageid?pageid:"";

    return GPOINTER_TO_INT(e_plugin_invoke(group->hook->hook.plugin, group->check, &hdata));
}

static void
ech_config_factory(EConfig *emp, gpointer data)
{
    struct _EConfigHookGroup *group = data;

    d(printf("config factory called %s\n", group->id?group->id:"all menus"));

    if (emp->target->type != group->target_type
        || !group->hook->hook.plugin->enabled)
        return;

    if (group->items)
        e_config_add_items(emp, group->items, ech_commit, ech_abort, NULL, group);

    if (group->check)
        e_config_add_page_check(emp, NULL, ech_check, group);
}

static void
emph_free_item(struct _EConfigItem *item)
{
    g_free(item->path);
    g_free(item->label);
    g_free(item->user_data);
    g_free(item);
}

static void
emph_free_group(struct _EConfigHookGroup *group)
{
    g_slist_foreach(group->items, (GFunc)emph_free_item, NULL);
    g_slist_free(group->items);

    g_free(group->id);
    g_free(group);
}

static GtkWidget *
ech_config_widget_factory(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, gpointer data)
{
    struct _EConfigHookGroup *group = data;

    if (group->hook->hook.plugin->enabled) {
        EConfigHookItemFactoryData hdata;

        hdata.config = ec;
        hdata.item = item;
        hdata.target = ec->target;
        hdata.parent = parent;
        hdata.old = old;

        return (GtkWidget *)e_plugin_invoke(group->hook->hook.plugin, (gchar *)item->user_data, &hdata);
    } else
        return NULL;
}

static struct _EConfigItem *
emph_construct_item(EPluginHook *eph, EConfigHookGroup *menu, xmlNodePtr root, EConfigHookTargetMap *map)
{
    struct _EConfigItem *item;

    d(printf("  loading config item\n"));
    item = g_malloc0(sizeof(*item));
    if ((item->type = e_plugin_hook_id(root, ech_item_types, "type")) == -1)
        goto error;
    item->path = e_plugin_xml_prop(root, "path");
    item->label = e_plugin_xml_prop_domain(root, "label", eph->plugin->domain);
    item->user_data = e_plugin_xml_prop(root, "factory");

    if (item->path == NULL
        || (item->label == NULL && item->user_data == NULL))
        goto error;

    if (item->user_data)
        item->factory = ech_config_widget_factory;

    d(printf("   path=%s label=%s factory=%s\n", item->path, item->label, (gchar *)item->user_data));

    return item;
error:
    d(printf("error!\n"));
    emph_free_item(item);
    return NULL;
}

static struct _EConfigHookGroup *
emph_construct_menu(EPluginHook *eph, xmlNodePtr root)
{
    struct _EConfigHookGroup *menu;
    xmlNodePtr node;
    EConfigHookTargetMap *map;
    EConfigHookClass *klass = (EConfigHookClass *)G_OBJECT_GET_CLASS(eph);
    gchar *tmp;

    d(printf(" loading menu\n"));
    menu = g_malloc0(sizeof(*menu));

    tmp = (gchar *)xmlGetProp(root, (const guchar *)"target");
    if (tmp == NULL)
        goto error;
    map = g_hash_table_lookup(klass->target_map, tmp);
    xmlFree(tmp);
    if (map == NULL)
        goto error;

    menu->target_type = map->id;
    menu->id = e_plugin_xml_prop(root, "id");
    if (menu->id == NULL) {
        g_warning("Plugin '%s' missing 'id' field in group for '%s'\n", eph->plugin->name,
              ((EPluginHookClass *)G_OBJECT_GET_CLASS(eph))->id);
        goto error;
    }
    menu->check = e_plugin_xml_prop(root, "check");
    menu->commit = e_plugin_xml_prop(root, "commit");
    menu->abort = e_plugin_xml_prop(root, "abort");
    menu->hook = (EConfigHook *)eph;
    node = root->children;
    while (node) {
        if (0 == strcmp((gchar *)node->name, "item")) {
            struct _EConfigItem *item;

            item = emph_construct_item(eph, menu, node, map);
            if (item)
                menu->items = g_slist_append(menu->items, item);
        }
        node = node->next;
    }

    return menu;
error:
    emph_free_group(menu);
    return NULL;
}

static gint
emph_construct(EPluginHook *eph, EPlugin *ep, xmlNodePtr root)
{
    xmlNodePtr node;
    EConfigClass *klass;

    d(printf("loading config hook\n"));

    if (((EPluginHookClass *)emph_parent_class)->construct(eph, ep, root) == -1)
        return -1;

    klass = ((EConfigHookClass *)G_OBJECT_GET_CLASS(eph))->config_class;

    node = root->children;
    while (node) {
        if (strcmp((gchar *)node->name, "group") == 0) {
            struct _EConfigHookGroup *group;

            group = emph_construct_menu(eph, node);
            if (group) {
                e_config_class_add_factory(klass, group->id, ech_config_factory, group);
                emph->groups = g_slist_append(emph->groups, group);
            }
        }
        node = node->next;
    }

    eph->plugin = ep;

    return 0;
}

static void
emph_finalise(GObject *o)
{
    EPluginHook *eph = (EPluginHook *)o;

    g_slist_foreach(emph->groups, (GFunc)emph_free_group, NULL);
    g_slist_free(emph->groups);

    ((GObjectClass *)emph_parent_class)->finalize(o);
}

static void
emph_class_init(EPluginHookClass *klass)
{
    ((GObjectClass *)klass)->finalize = emph_finalise;
    klass->construct = emph_construct;

    /* this is actually an abstract implementation but list it anyway */
    klass->id = "org.gnome.evolution.config:1.0";

    d(printf("EConfigHook: init class %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type)));

    ((EConfigHookClass *)klass)->target_map = g_hash_table_new(g_str_hash, g_str_equal);
    ((EConfigHookClass *)klass)->config_class = g_type_class_ref(e_config_get_type());
}

/**
 * e_config_hook_get_type:
 *
 * Standard GObject function to get the object type.
 *
 * Return value: The EConfigHook class type.
 **/
GType
e_config_hook_get_type(void)
{
    static GType type = 0;

    if (!type) {
        static const GTypeInfo info = {
            sizeof(EConfigHookClass), NULL, NULL, (GClassInitFunc) emph_class_init, NULL, NULL,
            sizeof(EConfigHook), 0, (GInstanceInitFunc) NULL,
        };

        emph_parent_class = g_type_class_ref(e_plugin_hook_get_type());
        type = g_type_register_static(e_plugin_hook_get_type(), "EConfigHook", &info, 0);
    }

    return type;
}

/**
 * e_config_hook_class_add_target_map:
 *
 * @klass: The dervied EconfigHook class.
 * @map: A map used to describe a single EConfigTarget type for this
 * class.
 *
 * Add a targe tmap to a concrete derived class of EConfig.  The
 * target map enumates the target types available for the implenting
 * class.
 **/
void
e_config_hook_class_add_target_map (EConfigHookClass *klass,
                                    const EConfigHookTargetMap *map)
{
    g_hash_table_insert(klass->target_map, (gpointer)map->type, (gpointer)map);
}