/*****************************************************************************
 * gtk_preferences.c: functions to handle the preferences dialog box.
 *****************************************************************************
 * Copyright (C) 2000, 2001 VideoLAN
 * $Id: gtk_preferences.c,v 1.33 2002/06/11 09:44:21 gbazin Exp $
 *
 * Authors: Gildas Bazin <gbazin@netcourrier.com>
 *          Lo�c Minier <lool@via.ecp.fr>
 *
 * 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 of the License, 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, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble: Our main job is to build a nice interface from the modules config
 *   structure. Once this is done, we need to track each change made by the
 *   user to the data contained in this interface so that when/if he decides to
 *   apply his changes we can quickly commit them into the modules config
 *   structure. (for this last task we use a GHashTable to accumulate the
 *   changes. To commit them, we then just have to circle through it )
 *
 *****************************************************************************/
#include <sys/types.h>                                              /* off_t */
#include <stdlib.h>

#include <vlc/vlc.h>
#include <vlc/intf.h>

#ifdef MODULE_NAME_IS_gnome
#   include <gnome.h>
#else
#   include <gtk/gtk.h>
#endif

#include <string.h>

#include "gtk_support.h"
#include "gtk_common.h"
#include "gtk_preferences.h"

/* local functions */
static void GtkCreateConfigDialog( char *, intf_thread_t * );

static void GtkConfigOk          ( GtkButton *, gpointer );
static void GtkConfigApply       ( GtkButton *, gpointer );
static void GtkConfigCancel      ( GtkButton *, gpointer );
static void GtkConfigSave        ( GtkButton *, gpointer );

static void GtkConfigDialogDestroyed ( GtkObject *, gpointer );

static void GtkStringChanged     ( GtkEditable *, gpointer );
static void GtkIntChanged        ( GtkEditable *, gpointer );
static void GtkFloatChanged      ( GtkEditable *, gpointer );
static void GtkBoolChanged       ( GtkToggleButton *, gpointer );

static void GtkFreeHashTable     ( GtkObject *object );
static void GtkFreeHashValue     ( gpointer, gpointer, gpointer );
static gboolean GtkSaveHashValue ( gpointer, gpointer, gpointer );

static void GtkModuleConfigure   ( GtkButton *, gpointer );
static void GtkModuleSelected    ( GtkButton *, gpointer );
static void GtkModuleHighlighted ( GtkCList *, int, int, GdkEventButton *,
                                   gpointer );

/****************************************************************************
 * Callback for menuitems: display configuration interface window
 ****************************************************************************/
void GtkPreferencesShow( GtkMenuItem * menuitem, gpointer user_data )
{
    intf_thread_t * p_intf;

    p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );

    GtkCreateConfigDialog( "main", p_intf );
}

/****************************************************************************
 * GtkCreateConfigDialog: dynamically creates the configuration dialog
 * box from all the configuration data provided by the selected module.
 ****************************************************************************/

/* create a new tooltipped area */
#define TOOLTIP( text )                                                   \
    /* create an event box to catch some events */                        \
    item_event_box = gtk_event_box_new();                                 \
    /* add a tooltip on mouseover */                                      \
    gtk_tooltips_set_tip( p_intf->p_sys->p_tooltips,                      \
                          item_event_box, text, "" );                     \
    gtk_container_set_border_width( GTK_CONTAINER(item_event_box), 4 );

/* draws a right aligned label in side of a widget */
#define LABEL_AND_WIDGET( label_text, widget, tooltip )                   \
    gtk_table_resize( GTK_TABLE(category_table), ++rows, 2 );             \
    item_align = gtk_alignment_new( 1, .5, 0, 0 );                        \
    item_label = gtk_label_new( label_text );                             \
    gtk_container_add( GTK_CONTAINER(item_align), item_label );           \
    gtk_table_attach_defaults( GTK_TABLE(category_table), item_align,     \
                               0, 1, rows - 1, rows );                    \
    item_align = gtk_alignment_new( 0, .5, .5, 0 );                       \
    gtk_container_add( GTK_CONTAINER(item_align), widget );               \
    TOOLTIP(tooltip)                                                      \
    gtk_container_add( GTK_CONTAINER(item_event_box), item_align );       \
    gtk_table_attach_defaults( GTK_TABLE(category_table), item_event_box, \
                               1, 2, rows - 1, rows );

static void GtkCreateConfigDialog( char *psz_module_name,
                                   intf_thread_t *p_intf )
{
    module_t *p_module, *p_module_bis;
    module_config_t *p_item;

    guint rows = 0;

    GHashTable *config_hash_table;

    GtkWidget *item_event_box;

    GtkWidget *config_dialog;
    GtkWidget *config_dialog_vbox;
    GtkWidget *config_notebook;

    GtkWidget *category_table = NULL;
    GtkWidget *category_label = NULL;

#ifndef MODULE_NAME_IS_gnome
    GtkWidget *dialog_action_area;
#endif

    GtkWidget *ok_button;
    GtkWidget *apply_button;
    GtkWidget *save_button;
    GtkWidget *cancel_button;

    GtkWidget *item_align;
    GtkWidget *item_frame;
    GtkWidget *item_hbox;
    GtkWidget *item_label;
    GtkWidget *item_vbox;
    GtkWidget *item_combo;
    GtkWidget *string_entry;
    GtkWidget *integer_spinbutton;
    GtkWidget *float_spinbutton;
    GtkObject *item_adj;
    GtkWidget *bool_checkbutton;
    GtkWidget *module_clist;
    GtkWidget *module_config_button;
    GtkWidget *module_select_button;

    gint category_max_height;

    /* Check if the dialog box is already opened because we don't want to
     * duplicate identical dialog windows. */
    config_dialog = (GtkWidget *)gtk_object_get_data(
                    GTK_OBJECT(p_intf->p_sys->p_window), psz_module_name );
    if( config_dialog )
    {
        /* Yeah it was open */
        gtk_widget_grab_focus( config_dialog );
        return;
    }


    /* Look for the selected module */
    for( p_module = p_intf->p_vlc->module_bank.first ; p_module != NULL ;
         p_module = p_module->next )
    {

        if( psz_module_name
             && !strcmp( psz_module_name, p_module->psz_object_name ) )
        {
            break;
        }
    }
    if( !p_module ) return;

    /* We found it, now we can start building its configuration interface */
    /* Create the configuration dialog box */

#ifdef MODULE_NAME_IS_gnome
    config_dialog = gnome_dialog_new( p_module->psz_longname, NULL );
    config_dialog_vbox = GNOME_DIALOG(config_dialog)->vbox;
#else
    config_dialog = gtk_dialog_new();
    gtk_window_set_title( GTK_WINDOW(config_dialog), p_module->psz_longname );
    config_dialog_vbox = GTK_DIALOG(config_dialog)->vbox;
#endif

    gtk_object_set_data( GTK_OBJECT(config_dialog), "p_intf", p_intf );

    category_max_height = config_GetInt( p_intf, MODULE_STRING "-prefs-maxh" );

    gtk_window_set_policy( GTK_WINDOW(config_dialog), TRUE, TRUE, FALSE );
    gtk_container_set_border_width( GTK_CONTAINER(config_dialog_vbox), 0 );

    /* Create our config hash table and associate it with the dialog box */
    config_hash_table = g_hash_table_new( NULL, NULL );
    gtk_object_set_data( GTK_OBJECT(config_dialog),
                         "config_hash_table", config_hash_table );

    /* Create notebook */
    config_notebook = gtk_notebook_new();
    gtk_notebook_set_scrollable( GTK_NOTEBOOK(config_notebook), TRUE );
    gtk_container_add( GTK_CONTAINER(config_dialog_vbox), config_notebook );

    /* Enumerate config options and add corresponding config boxes */
    p_item = p_module->p_config;
    do
    {
        switch( p_item->i_type )
        {

        case CONFIG_HINT_CATEGORY:
        case CONFIG_HINT_END:

            /*
             * Before we start building the interface for the new category, we
             * must close/finish the previous one we were generating.
             */
            if( category_table )
            {
                GtkWidget *_scrolled_window;
                GtkWidget *_viewport;
                GtkWidget *_vbox;
                GtkRequisition _requisition;

                /* create a vbox to deal with EXPAND/FILL issues in the
                 * notebook page, and pack it with the previously generated
                 * category_table */
                _vbox = gtk_vbox_new( FALSE, 0 );
                gtk_container_set_border_width( GTK_CONTAINER(_vbox), 4 );
                gtk_box_pack_start( GTK_BOX(_vbox), category_table,
                                    FALSE, FALSE, 0 );

                /* create a new scrolled window that will contain all of the
                 * above. */
                _scrolled_window = gtk_scrolled_window_new( NULL, NULL );
                gtk_scrolled_window_set_policy(
                    GTK_SCROLLED_WINDOW(_scrolled_window), GTK_POLICY_NEVER,
                    GTK_POLICY_AUTOMATIC );
                /* add scrolled window as a notebook page */
                gtk_notebook_append_page( GTK_NOTEBOOK(config_notebook),
                                          _scrolled_window, category_label );
                /* pack the vbox into the scrolled window */
                _viewport = gtk_viewport_new( NULL, NULL );
                gtk_viewport_set_shadow_type( GTK_VIEWPORT(_viewport),
                                              GTK_SHADOW_NONE );
                gtk_container_add( GTK_CONTAINER(_viewport), _vbox );
                gtk_container_add( GTK_CONTAINER(_scrolled_window),
                                   _viewport );

                /* set the size of the scrolled window to the size of the
                 * child widget */
                gtk_widget_show_all( _vbox );
                gtk_widget_size_request( _vbox, &_requisition );
                if( _requisition.height > category_max_height )
                    gtk_widget_set_usize( _scrolled_window, -1,
                                          category_max_height );
                else
                    gtk_widget_set_usize( _scrolled_window, -1,
                                          _requisition.height );

            }

            /*
             * Now we can start taking care of the new category
             */

            if( p_item->i_type == CONFIG_HINT_CATEGORY )
            {
                /* create a new table for right-left alignment of children */
                category_table = gtk_table_new( 0, 0, FALSE );
                gtk_table_set_col_spacings( GTK_TABLE(category_table), 4 );
                rows = 0;

                /* create a new category label */
                category_label = gtk_label_new( p_item->psz_text );
            }

            break;

        case CONFIG_ITEM_MODULE:

            item_frame = gtk_frame_new( p_item->psz_text );

            gtk_table_resize( GTK_TABLE(category_table), ++rows, 2 );
            gtk_table_attach_defaults( GTK_TABLE(category_table), item_frame,
                                       0, 2, rows - 1, rows );

            item_vbox = gtk_vbox_new( FALSE, 4 );
            gtk_container_add( GTK_CONTAINER(item_frame), item_vbox );

            /* create a new clist widget */
            {
                gchar * titles[] = { _("Name"), _("Description") };

                module_clist = gtk_clist_new_with_titles( 2, titles );
            }
            gtk_object_set_data( GTK_OBJECT(module_clist), "p_intf", p_intf );
            gtk_clist_column_titles_passive( GTK_CLIST(module_clist) );
            gtk_clist_set_selection_mode( GTK_CLIST(module_clist),
                                          GTK_SELECTION_SINGLE);
            gtk_container_add( GTK_CONTAINER(item_vbox), module_clist );

            /* build a list of available modules */
            {
                gchar * entry[2];

                for( p_module_bis = p_intf->p_vlc->module_bank.first ;
                     p_module_bis != NULL ;
                     p_module_bis = p_module_bis->next )
                {
                    if( p_module_bis->i_capabilities & (1 << p_item->i_value) )
                    {
                        entry[0] = p_module_bis->psz_object_name;
                        entry[1] = p_module_bis->psz_longname;
                        gtk_clist_append( GTK_CLIST(module_clist), entry );
                    }
                }
            }

            gtk_clist_set_column_auto_resize( GTK_CLIST(module_clist),
                                              0, TRUE );
            gtk_clist_set_column_auto_resize( GTK_CLIST(module_clist),
                                              1, TRUE );

            /* connect signals to the modules list */
            gtk_signal_connect( GTK_OBJECT(module_clist), "select_row",
                                GTK_SIGNAL_FUNC(GtkModuleHighlighted),
                                NULL );

            /* hbox holding the "select" and "configure" buttons */
            item_hbox = gtk_hbox_new( FALSE, 4 );
            gtk_container_add( GTK_CONTAINER(item_vbox), item_hbox);

            /* add configure button */
            module_config_button =
                gtk_button_new_with_label( _("Configure") );
            gtk_widget_set_sensitive( module_config_button, FALSE );
            gtk_container_add( GTK_CONTAINER(item_hbox),
                               module_config_button );
            gtk_object_set_data( GTK_OBJECT(module_config_button),
                                 "p_intf", p_intf );
            gtk_object_set_data( GTK_OBJECT(module_clist),
                                 "config_button", module_config_button );

            /* add select button */
            module_select_button =
                gtk_button_new_with_label( _("Select") );
            gtk_container_add( GTK_CONTAINER(item_hbox),
                               module_select_button );
            /* add a tooltip on mouseover */
            gtk_tooltips_set_tip( p_intf->p_sys->p_tooltips,
                                  module_select_button,
                                  p_item->psz_longtext, "" );

            /* hbox holding the "selected" label and text input */
            item_hbox = gtk_hbox_new( FALSE, 4 );
            gtk_container_add( GTK_CONTAINER(item_vbox), item_hbox);
            /* add new label */
            item_label = gtk_label_new( _("Selected:") );
            gtk_container_add( GTK_CONTAINER(item_hbox), item_label );

            /* add input box with default value */
            string_entry = gtk_entry_new();
            gtk_object_set_data( GTK_OBJECT(module_clist),
                                 "module_entry", string_entry );
            gtk_container_add( GTK_CONTAINER(item_hbox), string_entry );
            vlc_mutex_lock( p_item->p_lock );
            gtk_entry_set_text( GTK_ENTRY(string_entry),
                                p_item->psz_value ? p_item->psz_value : "" );
            vlc_mutex_unlock( p_item->p_lock );
            /* add a tooltip on mouseover */
            gtk_tooltips_set_tip( p_intf->p_sys->p_tooltips,
                                  string_entry, p_item->psz_longtext, "" );

            /* connect signals to the buttons */
            gtk_signal_connect( GTK_OBJECT(module_config_button), "clicked",
                                GTK_SIGNAL_FUNC(GtkModuleConfigure),
                                (gpointer)module_clist );
            gtk_signal_connect( GTK_OBJECT(module_select_button), "clicked",
                                GTK_SIGNAL_FUNC(GtkModuleSelected),
                                (gpointer)module_clist );

            /* connect signal to track changes in the text box */
            gtk_object_set_data( GTK_OBJECT(string_entry), "config_option",
                                 p_item->psz_name );
            gtk_signal_connect( GTK_OBJECT(string_entry), "changed",
                                GTK_SIGNAL_FUNC(GtkStringChanged),
                                (gpointer)config_dialog );
            break;

        case CONFIG_ITEM_STRING:
        case CONFIG_ITEM_FILE:

            if( !p_item->ppsz_list )
            {
                /* add input box with default value */
                item_combo = string_entry = gtk_entry_new();
            }
            else
            {
                /* add combo box with default value */
                GList *items = NULL;
                int i;

                for( i=0; p_item->ppsz_list[i]; i++ )
                    items = g_list_append( items, p_item->ppsz_list[i] );

                item_combo = gtk_combo_new();
                string_entry = GTK_COMBO(item_combo)->entry;
                gtk_combo_set_popdown_strings( GTK_COMBO(item_combo),
                                               items );

            }

            vlc_mutex_lock( p_item->p_lock );
            gtk_entry_set_text( GTK_ENTRY(string_entry),
                                p_item->psz_value ? p_item->psz_value : "" );
            vlc_mutex_unlock( p_item->p_lock );

            /* connect signal to track changes in the text box */
            gtk_object_set_data( GTK_OBJECT(string_entry), "config_option",
                                 p_item->psz_name );
            gtk_signal_connect( GTK_OBJECT(string_entry), "changed",
                                GTK_SIGNAL_FUNC(GtkStringChanged),
                                (gpointer)config_dialog );

            LABEL_AND_WIDGET( p_item->psz_text,
                              item_combo, p_item->psz_longtext );
            break;

        case CONFIG_ITEM_INTEGER:

            /* add input box with default value */
            item_adj = gtk_adjustment_new( p_item->i_value,
                                           -1, 99999, 1, 10, 10 );
            integer_spinbutton = gtk_spin_button_new( GTK_ADJUSTMENT(item_adj),
                                                      1, 0 );

            /* connect signal to track changes in the spinbutton value */
            gtk_object_set_data( GTK_OBJECT(integer_spinbutton),
                                 "config_option", p_item->psz_name );
            gtk_signal_connect( GTK_OBJECT(integer_spinbutton), "changed",
                                GTK_SIGNAL_FUNC(GtkIntChanged),
                                (gpointer)config_dialog );

            LABEL_AND_WIDGET( p_item->psz_text,
                              integer_spinbutton, p_item->psz_longtext );
            break;

        case CONFIG_ITEM_FLOAT:

            /* add input box with default value */
            item_adj = gtk_adjustment_new( p_item->f_value,
                                           0, 99999, 0.01, 10, 10 );
            float_spinbutton = gtk_spin_button_new( GTK_ADJUSTMENT(item_adj),
                                                    0.01, 2 );

            /* connect signal to track changes in the spinbutton value */
            gtk_object_set_data( GTK_OBJECT(float_spinbutton),
                                 "config_option", p_item->psz_name );
            gtk_signal_connect( GTK_OBJECT(float_spinbutton), "changed",
                                GTK_SIGNAL_FUNC(GtkFloatChanged),
                                (gpointer)config_dialog );

            LABEL_AND_WIDGET( p_item->psz_text,
                              float_spinbutton, p_item->psz_longtext );
            break;

        case CONFIG_ITEM_BOOL:

            /* add check button */
            bool_checkbutton = gtk_check_button_new();
            gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(bool_checkbutton),
                                          p_item->i_value );

            /* connect signal to track changes in the button state */
            gtk_object_set_data( GTK_OBJECT(bool_checkbutton), "config_option",
                                 p_item->psz_name );
            gtk_signal_connect( GTK_OBJECT(bool_checkbutton), "toggled",
                                GTK_SIGNAL_FUNC(GtkBoolChanged),
                                (gpointer)config_dialog );

            LABEL_AND_WIDGET( p_item->psz_text,
                              bool_checkbutton, p_item->psz_longtext );
            break;

        }

    }
    while( p_item->i_type != CONFIG_HINT_END && p_item++ );

#ifndef MODULE_NAME_IS_gnome
    /* Now let's add the action buttons at the bottom of the page */
    dialog_action_area = GTK_DIALOG(config_dialog)->action_area;
    gtk_container_set_border_width( GTK_CONTAINER(dialog_action_area), 4 );

    /* add a new table for the config option */
    item_hbox = gtk_hbox_new( FALSE, 0 );
    gtk_box_pack_end( GTK_BOX(dialog_action_area), item_hbox,
                      TRUE, FALSE, 0 );
    item_hbox = gtk_hbox_new( FALSE, 0 );
    gtk_box_pack_end( GTK_BOX(dialog_action_area), item_hbox,
                      TRUE, FALSE, 0 );
#endif

    /* Create the OK button */
#ifdef MODULE_NAME_IS_gnome
    gnome_dialog_append_button( GNOME_DIALOG(config_dialog),
                                GNOME_STOCK_BUTTON_OK );
    ok_button =
        GTK_WIDGET(g_list_last(GNOME_DIALOG(config_dialog)->buttons)->data);

    gnome_dialog_append_button( GNOME_DIALOG(config_dialog),
                                GNOME_STOCK_BUTTON_APPLY );
    apply_button =
        GTK_WIDGET(g_list_last(GNOME_DIALOG(config_dialog)->buttons)->data);

    gnome_dialog_append_button_with_pixmap(
        GNOME_DIALOG(config_dialog), _("Save"), GNOME_STOCK_PIXMAP_SAVE );
    save_button =
        GTK_WIDGET(g_list_last(GNOME_DIALOG(config_dialog)->buttons)->data);

    gnome_dialog_append_button( GNOME_DIALOG(config_dialog),
                                GNOME_STOCK_BUTTON_CANCEL );
    cancel_button =
        GTK_WIDGET(g_list_last(GNOME_DIALOG(config_dialog)->buttons)->data);
#else
    ok_button = gtk_button_new_with_label( _("OK") );
    gtk_box_pack_start( GTK_BOX(dialog_action_area), ok_button,
                        TRUE, TRUE, 0 );

    apply_button = gtk_button_new_with_label( _("Apply") );
    gtk_box_pack_start( GTK_BOX(dialog_action_area), apply_button,
                        TRUE, TRUE, 0 );

    save_button = gtk_button_new_with_label( _("Save") );
    gtk_box_pack_start( GTK_BOX(dialog_action_area), save_button,
                        TRUE, TRUE, 0 );

    cancel_button = gtk_button_new_with_label( _("Cancel") );
    gtk_box_pack_start( GTK_BOX(dialog_action_area), cancel_button,
                        TRUE, TRUE, 0 );
#endif

    gtk_signal_connect( GTK_OBJECT(ok_button), "clicked",
                        GTK_SIGNAL_FUNC(GtkConfigOk),
                        config_dialog );
    gtk_widget_set_sensitive( apply_button, FALSE );
    gtk_object_set_data( GTK_OBJECT(config_dialog), "apply_button",
                         apply_button );
    gtk_signal_connect( GTK_OBJECT(apply_button), "clicked",
                        GTK_SIGNAL_FUNC(GtkConfigApply),
                        config_dialog );
    gtk_signal_connect( GTK_OBJECT(save_button), "clicked",
                        GTK_SIGNAL_FUNC(GtkConfigSave),
                        config_dialog );
    gtk_signal_connect( GTK_OBJECT(cancel_button), "clicked",
                        GTK_SIGNAL_FUNC(GtkConfigCancel),
                        config_dialog );



    /* Ok, job done successfully. Let's keep a reference to the dialog box */
    gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_window),
                         psz_module_name, config_dialog );
    gtk_object_set_data( GTK_OBJECT(config_dialog), "psz_module_name",
                         psz_module_name );

    /* we want this ref to be destroyed if the object is destroyed */
    gtk_signal_connect( GTK_OBJECT(config_dialog), "destroy",
                       GTK_SIGNAL_FUNC(GtkConfigDialogDestroyed),
                       (gpointer)p_intf );

    gtk_widget_show_all( config_dialog );
}

#undef LABEL_AND_WIDGET
#undef TOOLTIP

/****************************************************************************
 * GtkConfigApply: store the changes to the config inside the modules
 * configuration structure and clear the hash table.
 ****************************************************************************/
void GtkConfigApply( GtkButton * button, gpointer user_data )
{
    intf_thread_t *p_intf;
    GHashTable *hash_table;
    GtkWidget *apply_button;

    hash_table = (GHashTable *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                    "config_hash_table" );
    p_intf = (intf_thread_t *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                   "p_intf" );
    g_hash_table_foreach_remove( hash_table, GtkSaveHashValue, (void*)p_intf );

    /* change the highlight status of the Apply button */
    apply_button = (GtkWidget *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                    "apply_button" );
    gtk_widget_set_sensitive( apply_button, FALSE );
}

void GtkConfigOk( GtkButton * button, gpointer user_data )
{
    GtkConfigApply( button, user_data );
    gtk_widget_destroy( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
}


void GtkConfigCancel( GtkButton * button, gpointer user_data )
{
    gtk_widget_destroy( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
}

void GtkConfigSave( GtkButton * button, gpointer user_data )
{
    intf_thread_t *p_intf;

    p_intf = (intf_thread_t *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                   "p_intf" );
    GtkConfigApply( button, user_data );
    config_SaveConfigFile( p_intf, NULL );
}

/****************************************************************************
 * GtkModuleHighlighted: display module description when an entry is selected
 *   in the clist, and activate the configure button if necessary.
 ****************************************************************************/
void GtkModuleHighlighted( GtkCList *module_clist, int row, int column,
                           GdkEventButton *event, gpointer user_data )
{
    intf_thread_t *p_intf;
    GtkWidget *config_button;
    module_t *p_module;
    char *psz_name;

    p_intf = (intf_thread_t *)gtk_object_get_data( GTK_OBJECT(module_clist),
                                                   "p_intf" );

    if( gtk_clist_get_text( GTK_CLIST(module_clist), row, 0, &psz_name ) )
    {
        /* look for module 'psz_name' */
        for( p_module = p_intf->p_vlc->module_bank.first ;
             p_module != NULL ;
             p_module = p_module->next )
        {
          if( !strcmp( p_module->psz_object_name, psz_name ) )
          {
              gtk_object_set_data( GTK_OBJECT(module_clist),
                                   "module_highlighted", p_module );
              config_button = gtk_object_get_data( GTK_OBJECT(module_clist),
                                                   "config_button" );
              if( p_module->i_config_items )
                  gtk_widget_set_sensitive( config_button, TRUE );
              else
                  gtk_widget_set_sensitive( config_button, FALSE );

              break;
          }
        }

    }
}

/****************************************************************************
 * GtkModuleConfigure: display module configuration dialog box.
 ****************************************************************************/
void GtkModuleConfigure( GtkButton *button, gpointer user_data )
{
    module_t *p_module;
    intf_thread_t *p_intf;

    p_module = (module_t *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                "module_highlighted" );

    if( !p_module ) return;
    p_intf = (intf_thread_t *)gtk_object_get_data( GTK_OBJECT(button),
                                                   "p_intf" );
    GtkCreateConfigDialog( p_module->psz_object_name, (gpointer)p_intf );

}

/****************************************************************************
 * GtkModuleSelected: select module.
 ****************************************************************************/
void GtkModuleSelected( GtkButton *button, gpointer user_data )
{
    module_t *p_module;
    GtkWidget *widget;

    p_module = (module_t *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                "module_highlighted" );
    widget = (GtkWidget *)gtk_object_get_data( GTK_OBJECT(user_data),
                                               "module_entry" );
    if( !p_module ) return;

    gtk_entry_set_text( GTK_ENTRY(widget), p_module->psz_object_name );

}

/****************************************************************************
 * GtkStringChanged: signal called when the user changes a string value.
 ****************************************************************************/
static void GtkStringChanged( GtkEditable *editable, gpointer user_data )
{
    intf_thread_t *p_intf;
    module_config_t *p_config;
    GHashTable *hash_table;
    GtkWidget *apply_button;

    p_intf = (intf_thread_t *)gtk_object_get_data( GTK_OBJECT(editable),
                                                   "p_intf" );
    hash_table = (GHashTable *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                    "config_hash_table" );
    /* free old p_config */
    p_config = (module_config_t *)g_hash_table_lookup( hash_table,
                                                       (gpointer)editable );
    if( p_config ) GtkFreeHashValue( NULL, (gpointer)p_config, (void *)p_intf );

    p_config = malloc( sizeof(module_config_t) );
    p_config->i_type = CONFIG_ITEM_STRING;
    p_config->psz_value = gtk_editable_get_chars( editable, 0, -1 );
    p_config->psz_name = (char *)gtk_object_get_data( GTK_OBJECT(editable),
                                                      "config_option" );

    g_hash_table_insert( hash_table, (gpointer)editable,
                         (gpointer)p_config );

    /* change the highlight status of the Apply button */
    apply_button = (GtkWidget *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                    "apply_button" );
    gtk_widget_set_sensitive( apply_button, TRUE );
}

/****************************************************************************
 * GtkIntChanged: signal called when the user changes an integer value.
 ****************************************************************************/
static void GtkIntChanged( GtkEditable *editable, gpointer user_data )
{
    intf_thread_t *p_intf;
    module_config_t *p_config;
    GHashTable *hash_table;
    GtkWidget *apply_button;

    p_intf = (intf_thread_t *)gtk_object_get_data( GTK_OBJECT(editable),
                                                   "p_intf" );
    gtk_spin_button_update( GTK_SPIN_BUTTON(editable) );

    hash_table = (GHashTable *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                    "config_hash_table" );

    /* free old p_config */
    p_config = (module_config_t *)g_hash_table_lookup( hash_table,
                                                       (gpointer)editable );
    if( p_config ) GtkFreeHashValue( NULL, (gpointer)p_config, (void *)p_intf );

    p_config = malloc( sizeof(module_config_t) );
    p_config->i_type = CONFIG_ITEM_INTEGER;
    p_config->i_value = gtk_spin_button_get_value_as_int(
                            GTK_SPIN_BUTTON(editable) );
    p_config->psz_name = (char *)gtk_object_get_data( GTK_OBJECT(editable),
                                                      "config_option" );

    g_hash_table_insert( hash_table, (gpointer)editable,
                         (gpointer)p_config );

    /* change the highlight status of the Apply button */
    apply_button = (GtkWidget *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                    "apply_button" );
    gtk_widget_set_sensitive( apply_button, TRUE );
}

/****************************************************************************
 * GtkFloatChanged: signal called when the user changes a float value.
 ****************************************************************************/
static void GtkFloatChanged( GtkEditable *editable, gpointer user_data )
{
    intf_thread_t *p_intf;
    module_config_t *p_config;
    GHashTable *hash_table;
    GtkWidget *apply_button;

    p_intf = (intf_thread_t *)gtk_object_get_data( GTK_OBJECT(editable),
                                                   "p_intf" );
    gtk_spin_button_update( GTK_SPIN_BUTTON(editable) );

    hash_table = (GHashTable *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                    "config_hash_table" );

    /* free old p_config */
    p_config = (module_config_t *)g_hash_table_lookup( hash_table,
                                                       (gpointer)editable );
    if( p_config ) GtkFreeHashValue( NULL, (gpointer)p_config, (void *)p_intf );

    p_config = malloc( sizeof(module_config_t) );
    p_config->i_type = CONFIG_ITEM_FLOAT;
    p_config->f_value = gtk_spin_button_get_value_as_float(
                           GTK_SPIN_BUTTON(editable) );
    p_config->psz_name = (char *)gtk_object_get_data( GTK_OBJECT(editable),
                                                      "config_option" );

    g_hash_table_insert( hash_table, (gpointer)editable,
                         (gpointer)p_config );

    /* change the highlight status of the Apply button */
    apply_button = (GtkWidget *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                    "apply_button" );
    gtk_widget_set_sensitive( apply_button, TRUE );
}

/****************************************************************************
 * GtkBoolChanged: signal called when the user changes a bool value.
 ****************************************************************************/
static void GtkBoolChanged( GtkToggleButton *button, gpointer user_data )
{
    intf_thread_t *p_intf;
    module_config_t *p_config;
    GHashTable *hash_table;
    GtkWidget *apply_button;

    p_intf = (intf_thread_t *)gtk_object_get_data( GTK_OBJECT(button),
                                                   "p_intf" );
    hash_table = (GHashTable *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                    "config_hash_table" );

    /* free old p_config */
    p_config = (module_config_t *)g_hash_table_lookup( hash_table,
                                                       (gpointer)button );
    if( p_config ) GtkFreeHashValue( NULL, (gpointer)p_config, (void *)p_intf );

    p_config = malloc( sizeof(module_config_t) );
    p_config->i_type = CONFIG_ITEM_BOOL;
    p_config->i_value = gtk_toggle_button_get_active( button );
    p_config->psz_name = (char *)gtk_object_get_data( GTK_OBJECT(button),
                                                      "config_option" );

    g_hash_table_insert( hash_table, (gpointer)button,
                         (gpointer)p_config );

    /* change the highlight status of the Apply button */
    apply_button = (GtkWidget *)gtk_object_get_data( GTK_OBJECT(user_data),
                                                     "apply_button" );
    gtk_widget_set_sensitive( apply_button, TRUE );
}

/****************************************************************************
 * GtkFreeHashTable: signal called when the config hash table is destroyed.
 ****************************************************************************/
static void GtkFreeHashTable( GtkObject *object )
{
    GHashTable *hash_table = (GHashTable *)gtk_object_get_data( object,
                                                         "config_hash_table" );
    intf_thread_t *p_intf = (intf_thread_t *)gtk_object_get_data( object,
                                                                  "p_intf" );

    g_hash_table_foreach( hash_table, GtkFreeHashValue, (void *)p_intf );
    g_hash_table_destroy( hash_table );
}

/****************************************************************************
 * GtkFreeHashValue: signal called when an element of the config hash table
 * is destroyed.
 ****************************************************************************/
static void GtkFreeHashValue( gpointer key, gpointer value, gpointer user_data)
{
    module_config_t * p_config = (module_config_t *)value;

    if( p_config->i_type == CONFIG_ITEM_STRING )
        if( p_config->psz_value ) g_free( p_config->psz_value );
    free( p_config );
}

/****************************************************************************
 * GtkSaveHashValue: callback used when enumerating the hash table in
 * GtkConfigApply().
 ****************************************************************************/
static gboolean GtkSaveHashValue( gpointer key, gpointer value,
                                  gpointer user_data )
{
    intf_thread_t *   p_intf   = (intf_thread_t *)user_data;
    module_config_t * p_config = (module_config_t *)value;

    switch( p_config->i_type )
    {

    case CONFIG_ITEM_STRING:
    case CONFIG_ITEM_FILE:
    case CONFIG_ITEM_MODULE:
        config_PutPsz( p_intf, p_config->psz_name,
                       *p_config->psz_value ? p_config->psz_value : NULL );
        break;
    case CONFIG_ITEM_INTEGER:
    case CONFIG_ITEM_BOOL:
        config_PutInt( p_intf, p_config->psz_name, p_config->i_value );
        break;
    case CONFIG_ITEM_FLOAT:
        config_PutFloat( p_intf, p_config->psz_name, p_config->f_value );
        break;
    }

    /* free the hash value we allocated */
    if( p_config->i_type == CONFIG_ITEM_STRING )
        g_free( p_config->psz_value );
    free( p_config );

    /* return TRUE so glib will free the hash entry */
    return TRUE;
}

/****************************************************************************
 * GtkConfigDialogDestroyed: callback triggered when the config dialog box is
 * destroyed.
 ****************************************************************************/
static void GtkConfigDialogDestroyed( GtkObject *object, gpointer user_data )
{
    intf_thread_t *p_intf = (intf_thread_t *)user_data;
    char *psz_module_name;

    psz_module_name = gtk_object_get_data( object, "psz_module_name" );

    /* remove the ref to the dialog box */
    gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_window),
                         psz_module_name, NULL );

    GtkFreeHashTable( object );
}