view src/evdev-plug/ed_ui.c @ 2545:585f2fc4134e

MIDI files were never properly supported (and probably shouldn't be, because we have separate and working plugins for handling MIDI files), thus references to MIDI are removed.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 30 Apr 2008 05:01:37 +0300
parents 145966acb0c4
children d45b4beadf6c
line wrap: on
line source

/*
*
* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*
*/

#include "ed_ui.h"
#include "ed_types.h"
#include "ed_internals.h"
#include "ed_actions.h"
#include "ed_bindings_store.h"
#include "ed_common.h"
#include <stdlib.h>
#include <string.h>
#include <linux/input.h>

#include <audacious/i18n.h>
#include <gtk/gtk.h>


static void cfg_ui_bindings_show ( ed_device_t * , GtkTreeRowReference * );

extern ed_action_t player_actions[];
extern gboolean plugin_is_active;

static GtkWidget *cfg_win = NULL;

enum
{
  DEVLIST_COL_ISACTIVE = 0,
  DEVLIST_COL_NAME,
  DEVLIST_COL_FILENAME,
  DEVLIST_COL_PHYS,
  DEVLIST_COL_ISAVAILABLE,
  DEVLIST_COL_BINDINGS,
  DEVLIST_NUMCOLS
};

enum
{
  DEVLIST_ISAVAILABLE_NOTDET = 0,
  DEVLIST_ISAVAILABLE_DET,
  DEVLIST_ISAVAILABLE_CUSTOM
};



static void
cfg_device_lv_populate( GtkListStore * store )
{
  /* store is logically divided in three families of devices,
     depending on the ISAVAILABLE value;
     DEVLIST_ISAVAILABLE_NOTDET -> not detected devices: these are not-custom
         devices that are present in configuration file but they do not seem
         to be plugged at the moment, cause they're not in system_devices_list;
     DEVLIST_ISAVAILABLE_DET -> detected devices: these are not-custom devices that
         are currently plugged, no matter whether they are in configuration or not;
     DEVLIST_ISAVAILABLE_CUSTOM -> custom devices: defined by user in configuration
         file, these are not checked against system_devices_list;
  */

  GList *system_devices_list = NULL;
  GList *config_devices_list = NULL;
  GList *list_iter = NULL;

  system_devices_list = ed_device_get_list_from_system();
  config_devices_list = ed_device_get_list_from_config();

  /* first, for each not-custom device in config_devices_list,
     perform a search in system_devices_list */
  list_iter = config_devices_list;
  while ( list_iter != NULL )
  {
    ed_device_info_t *info_from_cfg = list_iter->data;
    if ( info_from_cfg->is_custom == 0 ) /* only try to detect not-custom devices */
    {
      /* note: if device is found, filename and phys in info_from_cfg could be updated by the
               check; the found device will be marked as "found" in system_devices_list too */
      if ( ed_device_check( system_devices_list ,
           info_from_cfg->name , &(info_from_cfg->filename) , &(info_from_cfg->phys) ) == ED_DEVCHECK_OK )
      {
        info_from_cfg->reg = 1; /* mark our device from config as "found" */
      }
    }
    list_iter = g_list_next( list_iter );
  }

  /* at this point, we have three logical groups:
     #1 - not-custom devices in config_devices_list, marked as "found" -> DEVLIST_ISAVAILABLE_DET
     #2 - not-custom devices in config_devices_list, not-marked -> DEVLIST_ISAVAILABLE_NOTDET
     #3 - custom devices in config_devices_list -> DEVLIST_ISAVAILABLE_CUSTOM
     #1bis - plus, we pick not-marked devices from system_devices_list, which
             are plugged devices that are not present in config_devices_list -> DEVLIST_ISAVAILABLE_DET
  */

  /* let's start to fill the store with items from group #1 (DEVLIST_ISAVAILABLE_DET) */
  list_iter = config_devices_list;
  while ( list_iter != NULL )
  {
    ed_device_info_t *info_from_cfg = list_iter->data;
    if (( info_from_cfg->reg == 1 ) && ( info_from_cfg->is_custom == 0 ))
    {
      GtkTreeIter iter;
      gtk_list_store_append( store , &iter );
      gtk_list_store_set( store , &iter ,
        DEVLIST_COL_ISACTIVE , info_from_cfg->is_active ,
        DEVLIST_COL_NAME , info_from_cfg->name ,
        DEVLIST_COL_FILENAME , info_from_cfg->filename ,
        DEVLIST_COL_PHYS , info_from_cfg->phys ,
        DEVLIST_COL_ISAVAILABLE , DEVLIST_ISAVAILABLE_DET ,
        DEVLIST_COL_BINDINGS , info_from_cfg->bindings ,
        -1 );
    }
    list_iter = g_list_next( list_iter );
  }

  /* add items from group #1bis (DEVLIST_ISAVAILABLE_DET) */
  list_iter = system_devices_list;
  while ( list_iter != NULL )
  {
    ed_device_info_t *info_from_sys = list_iter->data;
    if ( info_from_sys->reg == 0 )
    {
      GtkTreeIter iter;
      gtk_list_store_append( store , &iter );
      gtk_list_store_set( store , &iter ,
        DEVLIST_COL_ISACTIVE , info_from_sys->is_active ,
        DEVLIST_COL_NAME , info_from_sys->name ,
        DEVLIST_COL_FILENAME , info_from_sys->filename ,
        DEVLIST_COL_PHYS , info_from_sys->phys ,
        DEVLIST_COL_ISAVAILABLE , DEVLIST_ISAVAILABLE_DET ,
        DEVLIST_COL_BINDINGS , info_from_sys->bindings ,
        -1 );
    }
    list_iter = g_list_next( list_iter );
  }

  /* add items from group #2 (DEVLIST_ISAVAILABLE_NOTDET) */
  list_iter = config_devices_list;
  while ( list_iter != NULL )
  {
    ed_device_info_t *info_from_cfg = list_iter->data;
    if (( info_from_cfg->reg == 0 ) && ( info_from_cfg->is_custom == 0 ))
    {
      GtkTreeIter iter;
      gtk_list_store_append( store , &iter );
      gtk_list_store_set( store , &iter ,
        DEVLIST_COL_ISACTIVE , info_from_cfg->is_active ,
        DEVLIST_COL_NAME , info_from_cfg->name ,
        DEVLIST_COL_FILENAME , info_from_cfg->filename ,
        DEVLIST_COL_PHYS , info_from_cfg->phys ,
        DEVLIST_COL_ISAVAILABLE , DEVLIST_ISAVAILABLE_NOTDET ,
        DEVLIST_COL_BINDINGS , info_from_cfg->bindings ,
        -1 );
    }
    list_iter = g_list_next( list_iter );
  }

  /* add items from group #3 (DEVLIST_ISAVAILABLE_CUSTOM) */
  list_iter = config_devices_list;
  while ( list_iter != NULL )
  {
    ed_device_info_t *info_from_cfg = list_iter->data;
    if ( info_from_cfg->is_custom == 1 )
    {
      GtkTreeIter iter;
      gtk_list_store_append( store , &iter );
      gtk_list_store_set( store , &iter ,
        DEVLIST_COL_ISACTIVE , info_from_cfg->is_active ,
        DEVLIST_COL_NAME , info_from_cfg->name ,
        DEVLIST_COL_FILENAME , info_from_cfg->filename ,
        DEVLIST_COL_PHYS , info_from_cfg->phys ,
        DEVLIST_COL_ISAVAILABLE , DEVLIST_ISAVAILABLE_CUSTOM ,
        DEVLIST_COL_BINDINGS , info_from_cfg->bindings ,
        -1 );
    }
    list_iter = g_list_next( list_iter );
  }

  /* delete lists; bindings objects are not deleted,
     they must be accessible from liststore for customization
     and they'll be deleted when the config win is destroyed */
  ed_device_free_list( config_devices_list );
  ed_device_free_list( system_devices_list );
  return;
}


static void
cfg_device_lv_celldatafunc_isavailable( GtkTreeViewColumn * col , GtkCellRenderer * renderer ,
                                        GtkTreeModel * model , GtkTreeIter * iter , gpointer data )
{
  guint is_available = 0;
  gtk_tree_model_get( model , iter , DEVLIST_COL_ISAVAILABLE , &is_available , -1 );
  switch (is_available)
  {
    case DEVLIST_ISAVAILABLE_DET:
      g_object_set( renderer , "text" , "Detected" ,
        "foreground" , "Green" , "foreground-set" , TRUE ,
        "background" , "Black" , "background-set" , TRUE , NULL );
      break;
    case DEVLIST_ISAVAILABLE_CUSTOM:
      g_object_set( renderer , "text" , "Custom" ,
        "foreground" , "Yellow" , "foreground-set" , TRUE ,
        "background" , "Black" , "background-set" , TRUE , NULL );
      break;
    case DEVLIST_ISAVAILABLE_NOTDET:
    default:
      g_object_set( renderer , "text" , "Not Detected" ,
        "foreground" , "Orange" , "foreground-set" , TRUE ,
        "background" , "Black" , "background-set" , TRUE , NULL );
      break;
  }
  return;
}


static void
cfg_device_lv_changetoggle ( GtkCellRendererToggle * toggle ,
                             gchar * path_str , gpointer cfg_device_lv )
{
  GtkTreeModel *model = gtk_tree_view_get_model( GTK_TREE_VIEW(cfg_device_lv) );
  GtkTreeIter iter;
  GtkTreePath *path = gtk_tree_path_new_from_string( path_str );
  gboolean toggled;

  gtk_tree_model_get_iter( model , &iter , path );
  gtk_tree_model_get( model , &iter , DEVLIST_COL_ISACTIVE , &toggled , -1 );

  toggled ^= 1;
  gtk_list_store_set( GTK_LIST_STORE(model), &iter , DEVLIST_COL_ISACTIVE , toggled , -1 );

  gtk_tree_path_free( path );
  return;
}


static void
cfg_config_cb_bindings_show ( gpointer cfg_device_lv )
{
  GtkTreeSelection *sel;
  GtkTreeModel *model;
  GtkTreeIter iter;

  /* check if a row is selected */
  sel = gtk_tree_view_get_selection( GTK_TREE_VIEW(cfg_device_lv) );
  if ( gtk_tree_selection_get_selected( sel , &model , &iter ) == TRUE )
  {
    /* check availability and active status */
    guint is_available = 0;
    gboolean is_active = FALSE;

    gtk_tree_model_get( model , &iter ,
      DEVLIST_COL_ISACTIVE , &is_active ,
      DEVLIST_COL_ISAVAILABLE , &is_available ,
      -1 );

    if ( is_available == DEVLIST_ISAVAILABLE_NOTDET )
    {
      /* do not open bindings window for a not-detected device */
      ed_ui_message_show( _("Information") ,
                          _("Cannot open bindings window for a not-detected device.\n"
                            "Ensure that the device has been correctly plugged in.") ,
                          cfg_win );
      return;
    }
    else
    {
      /* ok, create a ed_device_t item from selected row information */
      ed_device_t *dev;
      gchar *device_name = NULL, *device_file = NULL, *device_phys = NULL;
      gpointer bindings = NULL;

      gtk_tree_model_get( model , &iter ,
        DEVLIST_COL_NAME , &device_name ,
        DEVLIST_COL_FILENAME , &device_file ,
        DEVLIST_COL_PHYS , &device_phys ,
        DEVLIST_COL_BINDINGS , &bindings ,
        -1 );

      dev = ed_device_new(
               device_name , device_file , device_phys ,
               ( is_available == DEVLIST_ISAVAILABLE_CUSTOM ? 1 : 0 ) );
      g_free( device_name ); g_free( device_file ); g_free( device_phys );

      if ( dev != NULL )
      {
        GtkTreePath *path;
        GtkTreeRowReference *rowref;

        dev->info->bindings = bindings;

        path = gtk_tree_model_get_path( model , &iter );
        rowref = gtk_tree_row_reference_new( model , path );
        gtk_tree_path_free( path );
        /* show bindings window for device "dev"; also pass to the bindings window
           the GtkTreeRowReference (it keeps track of the model too) for selected row;
           if changes are committed in the bindings window, the row reference will be
           needed to update the treemodel with the new bindings information */
        cfg_ui_bindings_show( dev , rowref );
      }
      else
      {
        /* something went wrong */
        ed_ui_message_show( _("Error") ,
                            _("Unable to open selected device.\n"
                            "Please check read permissions on device file.") ,
                            cfg_win );
      }
    }
  }
}


static void
cfg_config_cb_addcustom_show ( gpointer cfg_device_lv )
{
  GtkWidget *addc_dlg;
  GtkWidget *addc_data_frame, *addc_data_vbox;
  GtkWidget *addc_data_label;
  GtkWidget *addc_data_table;
  GtkWidget *addc_data_name_label, *addc_data_name_entry;
  GtkWidget *addc_data_file_label, *addc_data_file_entry;
  gint result;
  gboolean task_done = FALSE;

  addc_dlg = gtk_dialog_new_with_buttons( _("EvDev-Plug - Add custom device") ,
               GTK_WINDOW(cfg_win) , GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT ,
               GTK_STOCK_CANCEL , GTK_RESPONSE_REJECT , GTK_STOCK_OK , GTK_RESPONSE_ACCEPT , NULL );

  addc_data_frame = gtk_frame_new( NULL );
  gtk_container_add( GTK_CONTAINER(GTK_DIALOG(addc_dlg)->vbox) , addc_data_frame );
  addc_data_vbox = gtk_vbox_new( FALSE , 5 );
  gtk_container_set_border_width( GTK_CONTAINER(addc_data_vbox) , 5 );
  gtk_container_add( GTK_CONTAINER(addc_data_frame) , addc_data_vbox );

  addc_data_label = gtk_label_new( "" );
  gtk_label_set_text( GTK_LABEL(addc_data_label) ,
                      _("EvDev-Plug tries to automatically detect and update information about\n"
                        "event devices available on the system.\n"
                        "However, if auto-detect doesn't work for your system, or you have event\n"
                        "devices in a non-standard location (currently they're only searched in\n"
                        "/dev/input/ ), you may want to add a custom device, explicitly specifying\n"
                        "name and device file.") );
  gtk_box_pack_start( GTK_BOX(addc_data_vbox) , addc_data_label , FALSE , FALSE , 0 );

  addc_data_name_label = gtk_label_new( _("Device name:") );
  gtk_misc_set_alignment( GTK_MISC(addc_data_name_label) , 0 , 0.5 );
  addc_data_name_entry = gtk_entry_new();

  addc_data_file_label = gtk_label_new( _("Device file:") );
  gtk_misc_set_alignment( GTK_MISC(addc_data_file_label) , 0 , 0.5 );
  addc_data_file_entry = gtk_entry_new();

  addc_data_table = gtk_table_new( 2 , 2 , FALSE );
  gtk_table_set_col_spacings( GTK_TABLE(addc_data_table) , 2 );
  gtk_table_set_row_spacings( GTK_TABLE(addc_data_table) , 2 );
  gtk_table_attach( GTK_TABLE(addc_data_table) , addc_data_name_label , 0 , 1 , 0 , 1 ,
                    GTK_FILL , GTK_EXPAND | GTK_FILL , 0 , 0 );
  gtk_table_attach( GTK_TABLE(addc_data_table) , addc_data_name_entry , 1 , 2 , 0 , 1 ,
                    GTK_EXPAND | GTK_FILL , GTK_EXPAND | GTK_FILL , 0 , 0 );
  gtk_table_attach( GTK_TABLE(addc_data_table) , addc_data_file_label , 0 , 1 , 1 , 2 ,
                    GTK_FILL , GTK_EXPAND | GTK_FILL , 0 , 0 );
  gtk_table_attach( GTK_TABLE(addc_data_table) , addc_data_file_entry , 1 , 2 , 1 , 2 ,
                    GTK_EXPAND | GTK_FILL , GTK_EXPAND | GTK_FILL , 0 , 0 );
  gtk_box_pack_start( GTK_BOX(addc_data_vbox) , addc_data_table , TRUE , TRUE , 0 );

  gtk_widget_show_all( addc_dlg );

  while ( task_done == FALSE )
  {
    result = gtk_dialog_run( GTK_DIALOG(addc_dlg) );
    if ( result == GTK_RESPONSE_ACCEPT )
    {
      const gchar *name = gtk_entry_get_text( GTK_ENTRY(addc_data_name_entry) );
      const gchar *file = gtk_entry_get_text( GTK_ENTRY(addc_data_file_entry) );
      if ( ( strcmp( name , "" ) != 0 ) &&
           ( strcmp( name , "___plugin___" ) != 0 ) &&
           ( strcmp( file , "" ) != 0 ) &&
           ( file[0] == '/' ) )
      {
        GtkTreeModel *model = gtk_tree_view_get_model( GTK_TREE_VIEW(cfg_device_lv) );
        GtkTreeIter iter;

        gtk_list_store_append( GTK_LIST_STORE(model) , &iter );
        gtk_list_store_set( GTK_LIST_STORE(model) , &iter ,
          DEVLIST_COL_ISACTIVE , FALSE ,
          DEVLIST_COL_NAME , name ,
          DEVLIST_COL_FILENAME , file ,
          DEVLIST_COL_PHYS , "(custom)" ,
          DEVLIST_COL_ISAVAILABLE , DEVLIST_ISAVAILABLE_CUSTOM ,
          DEVLIST_COL_BINDINGS , NULL , -1 );
        task_done = TRUE;
      }
      else
      {
        ed_ui_message_show( _("Information") ,
                            _("Please specify both name and filename.\n"
                              "Filename must be specified with absolute path.") ,
                            addc_dlg );
      }
    }
    else
      task_done = TRUE;
  }

  gtk_widget_destroy( addc_dlg );
}


static void
cfg_config_cb_remove ( gpointer cfg_device_lv )
{
  GtkTreeSelection *sel;
  GtkTreeModel *model;
  GtkTreeIter iter;

  /* check if a row is selected */
  sel = gtk_tree_view_get_selection( GTK_TREE_VIEW(cfg_device_lv) );
  if ( gtk_tree_selection_get_selected( sel , &model , &iter ) == TRUE )
  {
    guint is_available = 0;
    gtk_tree_model_get( model , &iter , DEVLIST_COL_ISAVAILABLE , &is_available , -1 );
    switch ( is_available )
    {
      case DEVLIST_ISAVAILABLE_NOTDET:
      {
        /* if remove is called on a not-detected device,
           it allows to remove existing configuration for it */
        GtkWidget *yesno_dlg = gtk_message_dialog_new( GTK_WINDOW(cfg_win) ,
          GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT , GTK_MESSAGE_QUESTION ,
          GTK_BUTTONS_YES_NO ,
          _("Do you want to remove the existing configuration for selected device?\n") );
        if ( gtk_dialog_run( GTK_DIALOG(yesno_dlg) ) == GTK_RESPONSE_YES )
        {
          gpointer bindings = NULL;
          gtk_tree_model_get( model , &iter , DEVLIST_COL_BINDINGS , &bindings , -1 );
          if ( bindings != NULL ) ed_bindings_store_delete( bindings );
          gtk_list_store_remove( GTK_LIST_STORE(model) , &iter );
        }
        gtk_widget_destroy( yesno_dlg );
        break;
      }

      case DEVLIST_ISAVAILABLE_CUSTOM:
      {
        /* if remove is called on a custom device,
           it allows to remove it along with its configuration */
        GtkWidget *yesno_dlg = gtk_message_dialog_new( GTK_WINDOW(cfg_win) ,
          GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT , GTK_MESSAGE_QUESTION ,
          GTK_BUTTONS_YES_NO ,
          _("Do you want to remove the selected custom device?\n") );
        if ( gtk_dialog_run( GTK_DIALOG(yesno_dlg) ) == GTK_RESPONSE_YES )
        {
          gpointer bindings = NULL;
          gtk_tree_model_get( model , &iter , DEVLIST_COL_BINDINGS , &bindings , -1 );
          if ( bindings != NULL ) ed_bindings_store_delete( bindings );
          gtk_list_store_remove( GTK_LIST_STORE(model) , &iter );
        }
        gtk_widget_destroy( yesno_dlg );
        break;
      }

      case DEVLIST_ISAVAILABLE_DET:
      default:
      {
        /* do nothing for detected devices */
        break;
      }
    }
  }
}


static gboolean
cfg_config_cb_bindings_commit_foreach ( GtkTreeModel * model ,
                                        GtkTreePath * path ,
                                        GtkTreeIter * iter ,
                                        gpointer config_device_list_p )
{
  gchar *device_name = NULL;
  gchar *device_file = NULL;
  gchar *device_phys = NULL;
  gboolean is_active = FALSE;
  guint is_available = 0;
  gpointer bindings = NULL;
  ed_device_info_t *info;
  GList **config_device_list = config_device_list_p;

  gtk_tree_model_get( model , iter ,
    DEVLIST_COL_ISACTIVE , &is_active ,
    DEVLIST_COL_NAME , &device_name ,
    DEVLIST_COL_FILENAME , &device_file ,
    DEVLIST_COL_PHYS , &device_phys ,
    DEVLIST_COL_ISAVAILABLE , &is_available ,
    DEVLIST_COL_BINDINGS , &bindings , -1 );

  info = ed_device_info_new( device_name , device_file , device_phys ,
           ( is_available == DEVLIST_ISAVAILABLE_CUSTOM ? 1 : 0 ) );
  info->bindings = bindings;
  info->is_active = is_active;

  *config_device_list = g_list_append( *config_device_list , info );
  return FALSE;
}


static gboolean
cfg_config_cb_bindings_delbindings_foreach ( GtkTreeModel * model ,
                                             GtkTreePath * path ,
                                             GtkTreeIter * iter ,
                                             gpointer data )
{
  gpointer bindings = NULL;
  gtk_tree_model_get( model , iter , DEVLIST_COL_BINDINGS , &bindings , -1 );
  if ( bindings != NULL )
    ed_bindings_store_delete( bindings );
  return FALSE;
}


static void
cfg_config_cb_commit ( gpointer cfg_device_lv )
{
  GList *config_device_list = NULL;
  GtkTreeModel *model;

  model = gtk_tree_view_get_model( GTK_TREE_VIEW(cfg_device_lv) );
  /* fill config_device_list with information from the treeview */
  gtk_tree_model_foreach( model , cfg_config_cb_bindings_commit_foreach , &config_device_list );
  /* ok, now we have a list of ed_device_info_t objects */

  /* commit changes in configuration file */
  ed_config_save_from_list( config_device_list );

  /* not needed anymore */
  ed_device_free_list( config_device_list );

  /* free bindings stored in the liststore */
  gtk_tree_model_foreach( model , cfg_config_cb_bindings_delbindings_foreach , NULL );

  if ( plugin_is_active == TRUE )
  {
    /* restart device listening according to the (possibly changed) config file */
    ed_device_start_listening_from_config();
  }

  gtk_widget_destroy( cfg_win );
}


static void
cfg_config_cb_cancel ( gpointer cfg_device_lv )
{
  GtkTreeModel *model;
  model = gtk_tree_view_get_model( GTK_TREE_VIEW(cfg_device_lv) );

  /* free bindings stored in the liststore */
  gtk_tree_model_foreach( model , cfg_config_cb_bindings_delbindings_foreach , NULL );

  if ( plugin_is_active == TRUE )
  {
    /* restart device listening according to the (unchanged) config file */
    ed_device_start_listening_from_config();
  }

  gtk_widget_destroy( cfg_win );
}


void
ed_ui_config_show( void )
{
  GtkWidget *cfg_vbox;
  GtkWidget *cfg_device_lv, *cfg_device_lv_frame, *cfg_device_lv_sw;
  GtkTreeViewColumn *cfg_device_lv_col_name, *cfg_device_lv_col_filename, *cfg_device_lv_col_phys;
  GtkTreeViewColumn *cfg_device_lv_col_isactive, *cfg_device_lv_col_isavailable;
  GtkCellRenderer *cfg_device_lv_rndr_text, *cfg_device_lv_rndr_toggle;
  GtkCellRenderer *cfg_device_lv_rndr_textphys, *cfg_device_lv_rndr_isavailable;
  GtkListStore *device_store;
  GtkWidget *cfg_bbar_hbbox;
  GtkWidget *cfg_bbar_bt_bind, *cfg_bbar_bt_addc, *cfg_bbar_bt_remc;
  GtkWidget *cfg_bbar_bt_cancel, *cfg_bbar_bt_ok;
  GdkGeometry cfg_win_hints;

  if ( cfg_win != NULL )
  {
    gtk_window_present( GTK_WINDOW(cfg_win) );
    return;
  }

  /* IMPORTANT: stop listening for all devices when config window is opened */
  ed_device_stop_listening_all( TRUE );

  cfg_win = gtk_window_new( GTK_WINDOW_TOPLEVEL );
  gtk_window_set_type_hint( GTK_WINDOW(cfg_win), GDK_WINDOW_TYPE_HINT_DIALOG );
  gtk_window_set_position( GTK_WINDOW(cfg_win), GTK_WIN_POS_CENTER );
  gtk_window_set_title( GTK_WINDOW(cfg_win), _("EvDev-Plug - Configuration") );
  gtk_container_set_border_width( GTK_CONTAINER(cfg_win), 10 );
  g_signal_connect( G_OBJECT(cfg_win) , "destroy" ,
                    G_CALLBACK(gtk_widget_destroyed) , &cfg_win );
  cfg_win_hints.min_width = -1;
  cfg_win_hints.min_height = 300;
  gtk_window_set_geometry_hints( GTK_WINDOW(cfg_win) , GTK_WIDGET(cfg_win) ,
                                 &cfg_win_hints , GDK_HINT_MIN_SIZE );

  cfg_vbox = gtk_vbox_new( FALSE , 0 );
  gtk_container_add( GTK_CONTAINER(cfg_win) , cfg_vbox );

  /* current liststore model
     ----------------------------------------------
     G_TYPE_BOOLEAN -> device listening on/off
     G_TYPE_STRING -> device name
     G_TYPE_STRING -> device filename
     G_TYPE_STRING -> device physical port
     G_TYPE_UINT -> device is available
     G_TYPE_POINTER -> device bindings
     ----------------------------------------------
  */
  device_store = gtk_list_store_new(
    DEVLIST_NUMCOLS , G_TYPE_BOOLEAN , G_TYPE_STRING ,
    G_TYPE_STRING , G_TYPE_STRING , G_TYPE_UINT , G_TYPE_POINTER );
  cfg_device_lv_populate( device_store );

  cfg_device_lv_frame = gtk_frame_new( NULL );
  cfg_device_lv = gtk_tree_view_new_with_model( GTK_TREE_MODEL(device_store) );
  g_object_unref( device_store );

  cfg_device_lv_rndr_text = gtk_cell_renderer_text_new();
  cfg_device_lv_rndr_toggle = gtk_cell_renderer_toggle_new();
  cfg_device_lv_rndr_isavailable = gtk_cell_renderer_text_new();
  cfg_device_lv_rndr_textphys = gtk_cell_renderer_text_new();
  g_object_set( G_OBJECT(cfg_device_lv_rndr_textphys) ,
                "ellipsize-set" , TRUE , "ellipsize" , PANGO_ELLIPSIZE_END , NULL );

  cfg_device_lv_col_isactive = gtk_tree_view_column_new_with_attributes(
    _("Active") , cfg_device_lv_rndr_toggle , "active" , DEVLIST_COL_ISACTIVE , NULL );
  gtk_tree_view_column_set_expand( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_isactive) , FALSE );
  gtk_tree_view_append_column( GTK_TREE_VIEW(cfg_device_lv), cfg_device_lv_col_isactive );
  cfg_device_lv_col_isavailable = gtk_tree_view_column_new();
  gtk_tree_view_column_set_title( cfg_device_lv_col_isavailable , _("Status") );
  gtk_tree_view_column_pack_start( cfg_device_lv_col_isavailable , cfg_device_lv_rndr_isavailable , TRUE );
  gtk_tree_view_column_set_cell_data_func(
    cfg_device_lv_col_isavailable , cfg_device_lv_rndr_isavailable ,
    (GtkTreeCellDataFunc)cfg_device_lv_celldatafunc_isavailable ,
    NULL , NULL );
  gtk_tree_view_column_set_expand( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_isavailable) , FALSE );
  gtk_tree_view_append_column( GTK_TREE_VIEW(cfg_device_lv), cfg_device_lv_col_isavailable );
  cfg_device_lv_col_name = gtk_tree_view_column_new_with_attributes(
    _("Device Name") , cfg_device_lv_rndr_text , "text" , DEVLIST_COL_NAME , NULL );
  gtk_tree_view_column_set_expand( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_name) , FALSE );
  gtk_tree_view_append_column( GTK_TREE_VIEW(cfg_device_lv), cfg_device_lv_col_name );
  cfg_device_lv_col_filename = gtk_tree_view_column_new_with_attributes(
    _("Device File") , cfg_device_lv_rndr_text , "text" , DEVLIST_COL_FILENAME , NULL );
  gtk_tree_view_column_set_expand( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_filename) , FALSE );
  gtk_tree_view_append_column( GTK_TREE_VIEW(cfg_device_lv), cfg_device_lv_col_filename );
  cfg_device_lv_col_phys = gtk_tree_view_column_new_with_attributes(
    _("Device Address") , cfg_device_lv_rndr_textphys , "text" , DEVLIST_COL_PHYS , NULL );
  gtk_tree_view_column_set_expand( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_phys) , TRUE );
  gtk_tree_view_column_set_resizable( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_phys) , TRUE );
  gtk_tree_view_append_column( GTK_TREE_VIEW(cfg_device_lv), cfg_device_lv_col_phys );
  cfg_device_lv_sw = gtk_scrolled_window_new( NULL , NULL );
  gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(cfg_device_lv_sw) ,
                                  GTK_POLICY_NEVER , GTK_POLICY_ALWAYS );

  gtk_container_add( GTK_CONTAINER(cfg_device_lv_sw) , cfg_device_lv );
  gtk_container_add( GTK_CONTAINER(cfg_device_lv_frame) , cfg_device_lv_sw );
  gtk_box_pack_start( GTK_BOX(cfg_vbox) , cfg_device_lv_frame , TRUE , TRUE , 0 );

  gtk_box_pack_start( GTK_BOX(cfg_vbox) , gtk_hseparator_new() , FALSE , FALSE , 4 );

  /* button bar */
  cfg_bbar_hbbox = gtk_hbutton_box_new();
  gtk_button_box_set_layout( GTK_BUTTON_BOX(cfg_bbar_hbbox) , GTK_BUTTONBOX_START );
  cfg_bbar_bt_bind = gtk_button_new_with_mnemonic( _("_Bindings") );
  gtk_button_set_image( GTK_BUTTON(cfg_bbar_bt_bind) ,
    gtk_image_new_from_stock( GTK_STOCK_PREFERENCES , GTK_ICON_SIZE_BUTTON ) );
  cfg_bbar_bt_addc = gtk_button_new_from_stock( GTK_STOCK_ADD );
  cfg_bbar_bt_remc = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
  /*cfg_bbar_bt_refresh = gtk_button_new_from_stock( GTK_STOCK_REFRESH );*/
  cfg_bbar_bt_cancel = gtk_button_new_from_stock( GTK_STOCK_CANCEL );
  cfg_bbar_bt_ok = gtk_button_new_from_stock( GTK_STOCK_OK );
  gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_bind );
  gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_addc );
  gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_remc );
  /*gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_refresh );*/
  gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_cancel );
  gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_ok );
  gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(cfg_bbar_hbbox) , cfg_bbar_bt_cancel , TRUE );
  gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(cfg_bbar_hbbox) , cfg_bbar_bt_ok , TRUE );
  gtk_box_pack_start( GTK_BOX(cfg_vbox) , cfg_bbar_hbbox , FALSE , FALSE , 0 );

  g_signal_connect( cfg_device_lv_rndr_toggle , "toggled" ,
                    G_CALLBACK(cfg_device_lv_changetoggle) , cfg_device_lv );
  g_signal_connect_swapped( G_OBJECT(cfg_bbar_bt_bind) , "clicked" ,
                            G_CALLBACK(cfg_config_cb_bindings_show) , cfg_device_lv );
  g_signal_connect_swapped( G_OBJECT(cfg_bbar_bt_addc) , "clicked" ,
                            G_CALLBACK(cfg_config_cb_addcustom_show) , cfg_device_lv );
  g_signal_connect_swapped( G_OBJECT(cfg_bbar_bt_remc) , "clicked" ,
                            G_CALLBACK(cfg_config_cb_remove) , cfg_device_lv );
  g_signal_connect_swapped( G_OBJECT(cfg_bbar_bt_cancel) , "clicked" ,
                            G_CALLBACK(cfg_config_cb_cancel) , cfg_device_lv );
  g_signal_connect_swapped( G_OBJECT(cfg_bbar_bt_ok) , "clicked" ,
                            G_CALLBACK(cfg_config_cb_commit) , cfg_device_lv );

  gtk_widget_show_all( cfg_win );
}



/* bindings window */

static void cfg_bindbox_new_empty_row( GtkTable * , GtkWidget * , gboolean );

enum
{
  BINDLIST_COL_COMBO_ACTION = 0,
  BINDLIST_COL_BT_ASSIGN,
  BINDLIST_COL_LABEL_EVCODE,
  BINDLIST_COL_BT_DELETE,
  BINDLIST_NUMCOLS
};


static gboolean
cfg_bindbox_assign_binding_timeout_func ( gpointer bindings_win )
{
  GtkWidget *trigger_dlg =  g_object_get_data( G_OBJECT(bindings_win) , "trigger-win" );
  if ( trigger_dlg != NULL )
    gtk_dialog_response( GTK_DIALOG(trigger_dlg) , GTK_RESPONSE_CANCEL );

  return FALSE;
}


static gboolean
cfg_bindbox_assign_binding_input_func ( GIOChannel * iochan ,
                                        GIOCondition cond ,
                                        gpointer bindings_win )
{
  switch ( cond )
  {
    case G_IO_IN:
    {
      gsize rb = 0;
      struct input_event inputev;
      g_io_channel_read_chars( iochan , (gchar*)&inputev ,
                               sizeof(struct input_event) , &rb , NULL );
      if ( rb == sizeof(struct input_event) )
      {
        GtkWidget *trigger_dlg = g_object_get_data( G_OBJECT(bindings_win) , "trigger-win" );
        if ( trigger_dlg == NULL )
        {
          /* the trigger-dialog window is not open; ignore input */
          return TRUE;
        }
        else
        {
          /* currently, only care about events of type 'key' and 'absolute'
             TODO: should we handle some other event type as well? */
          switch ( inputev.type )
          {
            case EV_KEY:
            case EV_ABS:
            {
              /* the trigger-dialog window is open; record input and
               store it in a container managed by the trigger-dialog itself */
              ed_inputevent_t *dinputev = g_malloc(sizeof(ed_inputevent_t));
              dinputev->type = inputev.type;
              dinputev->code = inputev.code;
              dinputev->value = inputev.value;
              g_object_set_data( G_OBJECT(trigger_dlg) , "trigger-data" , dinputev );
              gtk_dialog_response( GTK_DIALOG(trigger_dlg) , GTK_RESPONSE_OK );
              break;
            }
          }
        }
      }
    }

  default:
    ;
  }
  return TRUE;
}


static gboolean
cfg_bindbox_assign_binding_checkdups( GtkWidget * table , ed_inputevent_t * inputev )
{
  /* check if inputev is already assigned in table */
  GList *children = GTK_TABLE(table)->children;
  for ( ; children != NULL ; children = g_list_next(children) )
  {
    GtkTableChild *child = children->data;

    if ( child->top_attach + 1 == GTK_TABLE(table)->nrows )
      continue; /* skip last empty row */

    if ( child->left_attach == BINDLIST_COL_LABEL_EVCODE )
    {
      /* it's a label, pick its inputevent */
      ed_inputevent_t * iev = g_object_get_data( G_OBJECT(child->widget) , "inputevent" );
      if ( ( iev != NULL ) && ( ed_inputevent_check_equality( inputev , iev ) == TRUE ) )
        return TRUE; /* a duplicate was found */
    }
  }
  return FALSE;
}


static void
cfg_bindbox_assign_binding ( GtkButton * assign_button , gpointer bindings_win )
{
  GtkWidget *trigger_dlg;
  GtkWidget *trigger_dlg_frame;
  GtkWidget *trigger_dlg_label;
  guint timeout_sid = 0;
  gint res = 0;

  /* create a not-decorated window to inform the user about what's going on */
  trigger_dlg = gtk_dialog_new();
  gtk_widget_set_name( trigger_dlg , "trigger_dlg" );
  trigger_dlg_label = gtk_label_new( _("Press a key of your device to bind it;\n"
                                    "if no key is pressed in five seconds, this window\n"
                                    "will close without binding changes.") );
  gtk_widget_hide( GTK_WIDGET(GTK_DIALOG(trigger_dlg)->action_area) );
  gtk_misc_set_padding( GTK_MISC(trigger_dlg_label) , 10 , 10 );
  trigger_dlg_frame = gtk_frame_new( NULL );
  gtk_container_add( GTK_CONTAINER(trigger_dlg_frame) , trigger_dlg_label );
  gtk_container_add( GTK_CONTAINER(GTK_DIALOG(trigger_dlg)->vbox) , trigger_dlg_frame );
  gtk_window_set_position( GTK_WINDOW(trigger_dlg) , GTK_WIN_POS_CENTER );
  gtk_window_set_decorated( GTK_WINDOW(trigger_dlg) , FALSE );
  gtk_window_set_transient_for( GTK_WINDOW(trigger_dlg) ,
    GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(assign_button))) );
  gtk_dialog_set_has_separator( GTK_DIALOG(trigger_dlg) , FALSE );
  gtk_widget_show_all( trigger_dlg );

  /* close the trigger-window if no input is received in 5 seconds */
  timeout_sid = g_timeout_add( 5000 , cfg_bindbox_assign_binding_timeout_func , bindings_win );

  g_object_set_data( G_OBJECT(bindings_win) , "trigger-win" , trigger_dlg ); /* enable trigger-listening */
  res = gtk_dialog_run( GTK_DIALOG(trigger_dlg) );
  g_object_set_data( G_OBJECT(bindings_win) , "trigger-win" , NULL ); /* stop trigger-listening */
  switch ( res )
  {
    case GTK_RESPONSE_OK:
    {
      ed_inputevent_t *dinputev = g_object_get_data( G_OBJECT(trigger_dlg) , "trigger-data" );
      GtkWidget *label = g_object_get_data( G_OBJECT(assign_button) , "label" );
      GtkWidget *table = g_object_get_data( G_OBJECT(bindings_win) , "table" );
      gchar *input_str;

      /* we got a new input event; ensure that this has not been assigned to an action already */
      if ( cfg_bindbox_assign_binding_checkdups( table , dinputev ) == TRUE )
      {
        /* this input event has been already used */
        g_free( dinputev );
        g_source_remove( timeout_sid );
        ed_ui_message_show( _("Information") ,
                            _("This input event has been already assigned.\n\n"
                              "It's not possible to assign multiple actions to the same input event "
                              "(although it's possible to assign the same action to multiple events).") ,
                            bindings_win );
      }
      else
      {
        /* if this is the last row ("Assign" button) , create a new empty row */
        if ( GPOINTER_TO_INT(g_object_get_data(G_OBJECT(assign_button),"last")) == 1 )
        {
          cfg_bindbox_new_empty_row( GTK_TABLE(table) , bindings_win , TRUE );
          /* so, the current row is not the last one anymore;
             remove its "last" mark and enable its delete button */
          g_object_set_data( G_OBJECT(assign_button) , "last" , GINT_TO_POINTER(0) );
          gtk_widget_set_sensitive( GTK_WIDGET(g_object_get_data(G_OBJECT(assign_button),"delbt")) , TRUE );
        }

        g_object_set_data_full( G_OBJECT(label) , "inputevent" , dinputev , g_free );
        input_str = g_strdup_printf( "%i:%i:%i" , dinputev->type , dinputev->code , dinputev->value );
        gtk_label_set_text( GTK_LABEL(label) , input_str );
        g_free( input_str );
        g_source_remove( timeout_sid );
      }

      break;
    }

    default:
    {
      break; /* nothing should be changed */
    }
  }

  gtk_widget_destroy( trigger_dlg );
}


static void
cfg_bindbox_delete_row( GtkButton * delete_button , gpointer bindings_win )
{
  GtkTable *oldtable, *newtable;
  GtkWidget *table_sw, *table_vp;
  GList *children = NULL;
  gint drow = 0;

  /* here comes the drawback of using GtkTable to display
     a list-like set of widgets :) deletion can be a complex task */

  /* first, get the row number of the row that should be deleted
     (this can't be the last one cause it has the delete button disabled) */
  oldtable = g_object_get_data( G_OBJECT(bindings_win) , "table" );
  children = oldtable->children;
  while ( children != NULL )
  {
    GtkTableChild *child = children->data;
    if ( child->widget == (GtkWidget*)delete_button )
      drow = child->top_attach;
    children = g_list_next( children );
  }

  /* now, a simple trick: create a new GtkTable and move all
     of the widgets there, excepting those positioned in row "drow" */
  newtable = (GtkTable*)gtk_table_new( oldtable->nrows - 1 , oldtable->ncols , FALSE );

  children = oldtable->children;
  while ( children != NULL )
  {
    GtkTableChild *child = children->data;
    if ( child->top_attach < drow )
    {
      GtkWidget *widget = child->widget;
      guint widget_row = child->top_attach;
      guint widget_col = child->left_attach;
      g_object_ref( widget );
      gtk_container_remove( GTK_CONTAINER(oldtable) , widget );
      gtk_table_attach( GTK_TABLE(newtable) , widget ,
        widget_col , widget_col + 1 , widget_row , widget_row + 1 ,
        (widget_col == BINDLIST_COL_LABEL_EVCODE ? GTK_EXPAND | GTK_FILL : GTK_FILL ) , GTK_FILL , 0 , 0 );
      /* gtk_container_remove() changed the structure used to iterate; restart */
      children = oldtable->children;
    }
    else if ( child->top_attach > drow )
    {
      GtkWidget *widget = child->widget;
      guint widget_row = child->top_attach;
      guint widget_col = child->left_attach;
      g_object_ref( widget );
      gtk_container_remove( GTK_CONTAINER(oldtable) , widget );
      gtk_table_attach( GTK_TABLE(newtable) , widget ,
        widget_col , widget_col + 1 , widget_row - 1 , widget_row ,
        (widget_col == BINDLIST_COL_LABEL_EVCODE ? GTK_EXPAND | GTK_FILL : GTK_FILL ) , GTK_FILL , 0 , 0 );
      /* gtk_container_remove() changed the structure used to iterate; restart */
      children = oldtable->children;
    }
    else
      children = g_list_next(children);
  }

  /* now, replace the old GtkTable with the new one */
  table_sw = g_object_get_data( G_OBJECT(bindings_win) , "tablesw" );
  table_vp = gtk_bin_get_child( GTK_BIN(table_sw) ); /* get the viewport */
  gtk_widget_destroy( GTK_WIDGET(oldtable) ); /* destroy old GtkTable */
  gtk_container_add( GTK_CONTAINER(table_vp) , GTK_WIDGET(newtable) );
  g_object_set_data( G_OBJECT(bindings_win) , "table" , newtable ); /* update table in bindings_win */
  gtk_widget_show_all( GTK_WIDGET(newtable) );
  return;
}


/* the three structure types defined below
   are only used in cfg_bindbox_populate process */
typedef struct
{
  GtkWidget *combobox;
  gint action_code;
}
combosas_helper_t;

typedef struct
{
  gint action_code;
  ed_inputevent_t * inputev;
}
popul_binding_t;

typedef struct
{
  gint id;
  popul_binding_t *bindings;
}
popul_container_binding_t;

static gboolean
cfg_bindbox_populate_foreach_combosas ( GtkTreeModel * model ,
                                        GtkTreePath * path ,
                                        GtkTreeIter * iter ,
                                        gpointer combosas_helper_gp )
{
  gint action_code = 0;
  combosas_helper_t *combosas_helper = combosas_helper_gp;
  gtk_tree_model_get( model , iter , 1 , &action_code , -1 );
  if ( action_code == combosas_helper->action_code )
  {
    gtk_combo_box_set_active_iter( GTK_COMBO_BOX(combosas_helper->combobox) , iter );
    return TRUE;
  }
  return FALSE;
}

static void
cfg_bindbox_populate_foreach ( ed_inputevent_t * sinputev ,
                               gint action_code ,
                               gpointer popul_container_gp ,
                               gpointer none )
{
  ed_inputevent_t *inputev;
  popul_container_binding_t *popul_container = popul_container_gp;

  /* make a copy of the ed_inputevent_t object "sinputev" */
  inputev = g_malloc(sizeof(ed_inputevent_t));
  inputev->type = sinputev->type;
  inputev->code = sinputev->code;
  inputev->value = sinputev->value;

  popul_container->bindings[popul_container->id].inputev = inputev;
  popul_container->bindings[popul_container->id].action_code = action_code;

  popul_container->id++;
  return;
}

static int
cfg_bindbox_populate_qsortfunc( const void * p_binding_a , const void * p_binding_b )
{
  return ((popul_binding_t*)p_binding_a)->action_code - ((popul_binding_t*)p_binding_b)->action_code;
}

static void
cfg_bindbox_populate( GtkWidget * bind_table , GtkWidget * bindings_win , gpointer bindings )
{
  if ( bindings != NULL )
  {
    guint bindings_num = ed_bindings_store_size( bindings );
    popul_binding_t *popul_binding = (popul_binding_t*)calloc( bindings_num , sizeof(popul_binding_t) );
    popul_container_binding_t *popul_container = g_malloc(sizeof(popul_container_binding_t));
    gint i = 0;

    popul_container->bindings = popul_binding;
    popul_container->id = 0;

    ed_bindings_store_foreach( bindings ,
      (ed_bindings_store_foreach_func)cfg_bindbox_populate_foreach , popul_container , NULL );

    /* ok, now we have a popul_binding array, reorder it using action_codes */
    qsort( popul_binding , bindings_num , sizeof(popul_binding_t) , cfg_bindbox_populate_qsortfunc );

    /* attach to table each popul_binding */
    for ( i = 0 ; i < bindings_num ; i++ )
    {
      GList *children = GTK_TABLE(bind_table)->children;
      for ( ; children != NULL ; children = g_list_next(children) )
      {
        GtkTableChild *child = children->data;
        if ( ( (child->top_attach + 1) == GTK_TABLE(bind_table)->nrows ) &&
             ( child->left_attach == BINDLIST_COL_BT_ASSIGN ) &&
             ( GPOINTER_TO_INT(g_object_get_data(G_OBJECT(child->widget),"last")) == 1 ) )
        {
          /* ok, this child->widget is the last assign button */
          GtkWidget *combobox = g_object_get_data(G_OBJECT(child->widget),"combobox");
          GtkWidget *label = g_object_get_data(G_OBJECT(child->widget),"label");
          GtkWidget *delbt = g_object_get_data(G_OBJECT(child->widget),"delbt");
          GtkTreeModel *combomodel;
          combosas_helper_t *combosas_helper;
          gchar *input_str;

          combomodel = gtk_combo_box_get_model( GTK_COMBO_BOX(combobox) );
          combosas_helper = g_malloc(sizeof(combosas_helper_t));
          combosas_helper->combobox = combobox;
          combosas_helper->action_code = popul_binding[i].action_code;
          gtk_tree_model_foreach( combomodel , cfg_bindbox_populate_foreach_combosas , combosas_helper );
          g_free( combosas_helper );

          g_object_set_data_full( G_OBJECT(label) , "inputevent" , popul_binding[i].inputev , g_free );
          input_str = g_strdup_printf( "%i:%i:%i" ,
            popul_binding[i].inputev->type ,
            popul_binding[i].inputev->code ,
            popul_binding[i].inputev->value );
          gtk_label_set_text( GTK_LABEL(label) , input_str );
          g_free( input_str );

          /* so, the current row wont' be the last one anymore;
             remove its "last" mark and enable its delete button */
          g_object_set_data( G_OBJECT(child->widget) , "last" , GINT_TO_POINTER(0) );
          gtk_widget_set_sensitive( delbt , TRUE );
          cfg_bindbox_new_empty_row( GTK_TABLE(bind_table) , bindings_win , TRUE );

        }
      }
      DEBUGMSG( "populating bindings window: code %i -> event %d:%d:%d\n" ,
        popul_binding[i].action_code , popul_binding[i].inputev->type,
        popul_binding[i].inputev->code, popul_binding[i].inputev->value );
    }
    g_free( popul_binding );
    g_free( popul_container );
  }
  return;
}


static void
cfg_bindbox_new_empty_row( GtkTable * bind_table , GtkWidget * bindings_win , gboolean resize_table )
{
  GtkWidget *action_combobox;
  GtkCellRenderer *action_combobox_rndr;
  GtkWidget *assign_button;
  GtkWidget *assign_label;
  GtkWidget *delete_button;

  /* add one row to the table */
  if ( resize_table == TRUE )
    gtk_table_resize( bind_table , bind_table->nrows + 1 , bind_table->ncols );

  /* action combobox */
  action_combobox = gtk_combo_box_new_with_model(
   GTK_TREE_MODEL(g_object_get_data(G_OBJECT(bindings_win),"action_store")) );
  action_combobox_rndr = gtk_cell_renderer_text_new();
  gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(action_combobox) , action_combobox_rndr , TRUE );
  gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(action_combobox) ,
    action_combobox_rndr , "text" , 0 , NULL );
  gtk_combo_box_set_active( GTK_COMBO_BOX(action_combobox) , 0 );

  gtk_table_attach( bind_table , action_combobox ,
    BINDLIST_COL_COMBO_ACTION , BINDLIST_COL_COMBO_ACTION + 1 ,
    bind_table->nrows - 1 , bind_table->nrows ,
    GTK_FILL , GTK_FILL , 0 , 0 );

  /* assign button */
  assign_button = gtk_button_new();
  gtk_button_set_image( GTK_BUTTON(assign_button) ,
                        gtk_image_new_from_stock( GTK_STOCK_EXECUTE , GTK_ICON_SIZE_BUTTON ) );
  g_object_set_data( G_OBJECT(assign_button) , "last" , GINT_TO_POINTER(1) );
  g_signal_connect( G_OBJECT(assign_button) , "clicked" ,
                    G_CALLBACK(cfg_bindbox_assign_binding) , bindings_win );

  gtk_table_attach( bind_table , assign_button ,
    BINDLIST_COL_BT_ASSIGN , BINDLIST_COL_BT_ASSIGN + 1 ,
    bind_table->nrows - 1 , bind_table->nrows ,
    GTK_FILL , GTK_FILL , 0 , 0 );

  /* assigned-code label */
  assign_label = gtk_label_new( "" );
  gtk_misc_set_alignment( GTK_MISC(assign_label) , 0 , 0.5 );
  gtk_misc_set_padding( GTK_MISC(assign_label) , 10 , 0 );
  g_object_set_data_full( G_OBJECT(assign_label) , "inputevent" , NULL , g_free );

  gtk_table_attach( bind_table , assign_label ,
    BINDLIST_COL_LABEL_EVCODE , BINDLIST_COL_LABEL_EVCODE +1 ,
    bind_table->nrows - 1 , bind_table->nrows ,
    GTK_EXPAND | GTK_FILL , GTK_FILL , 0 , 0 );

  /* delete button */
  delete_button = gtk_button_new();
  gtk_button_set_image( GTK_BUTTON(delete_button) ,
                        gtk_image_new_from_stock( GTK_STOCK_DELETE , GTK_ICON_SIZE_BUTTON ) );
  g_signal_connect( G_OBJECT(delete_button) , "clicked" ,
                    G_CALLBACK(cfg_bindbox_delete_row) , bindings_win );
  gtk_widget_set_sensitive( delete_button , FALSE );

  gtk_table_attach( bind_table , delete_button ,
    BINDLIST_COL_BT_DELETE , BINDLIST_COL_BT_DELETE + 1 ,
    bind_table->nrows - 1 , bind_table->nrows ,
    GTK_FILL , GTK_FILL , 0 , 0 );

  /* data for assign button */
  g_object_set_data( G_OBJECT(assign_button) , "combobox" , action_combobox );
  g_object_set_data( G_OBJECT(assign_button) , "label" , assign_label );
  g_object_set_data( G_OBJECT(assign_button) , "delbt" , delete_button );

  gtk_widget_show_all( GTK_WIDGET(bind_table) );
  return;
}


static void
cfg_bindings_cb_destroy ( GtkObject * bindings_win , gpointer dev_gp )
{
  ed_device_t *dev = dev_gp;
  /* bindings window is going to be destroyed;
     deactivate device listening and free the ed_device_t object */
  g_source_remove( dev->iochan_sid );
  ed_device_delete( dev );

  /* the rowreference is no longer needed as well */
  gtk_tree_row_reference_free(
    (GtkTreeRowReference*)g_object_get_data(G_OBJECT(bindings_win),"rowref") );
  return;
}


static void
cfg_bindings_cb_commit ( gpointer bindings_win )
{
  GtkTreeRowReference *rowref = g_object_get_data(G_OBJECT(bindings_win),"rowref");
  if ( gtk_tree_row_reference_valid( rowref ) == TRUE )
  {
    GtkTreeModel *model = gtk_tree_row_reference_get_model( rowref );
    GtkTreePath *path = gtk_tree_row_reference_get_path( rowref );
    GtkTreeIter iter;
    GtkTable *table;
    gpointer new_bindings = NULL, old_bindings = NULL;

    /****************************************************************************/
    table = g_object_get_data( G_OBJECT(bindings_win) , "table" );
    if ( table->nrows > 1 )
    {
      /* bindings defined */
      GList *children = table->children;
      gint *array_actioncode;
      ed_inputevent_t **array_inputevent;
      gint i = 0;

      array_actioncode = calloc( table->nrows - 1 , sizeof(gint) );
      array_inputevent = calloc( table->nrows - 1 , sizeof(ed_inputevent_t*) );

      for ( ; children != NULL ; children = g_list_next( children ) )
      {
        /* pick information from relevant table cells and put them in arrays */
        GtkTableChild *child = children->data;

        if ( ( child->top_attach + 1 ) == table->nrows )
          continue; /* skip last empty row */

        if ( child->left_attach == BINDLIST_COL_COMBO_ACTION )
        {
          GtkTreeModel *combomodel = gtk_combo_box_get_model( GTK_COMBO_BOX(child->widget) );
          GtkTreeIter comboiter;
          gint actioncode = 0;

          gtk_combo_box_get_active_iter( GTK_COMBO_BOX(child->widget) , &comboiter );
          gtk_tree_model_get( combomodel , &comboiter , 1 , &actioncode , -1 ); /* pick the action code */
          array_actioncode[child->top_attach] = actioncode;
        }
        else if ( child->left_attach == BINDLIST_COL_LABEL_EVCODE )
        {
          ed_inputevent_t *inputevent = g_object_get_data( G_OBJECT(child->widget) , "inputevent" );
          array_inputevent[child->top_attach] = inputevent;
        }
      }

      /* ok, now copy data from arrays to the bindings store */
      new_bindings = ed_bindings_store_new(); /* new bindings store object */

      for ( i = 0 ; i < ( table->nrows - 1 ) ; i++ )
        ed_bindings_store_insert( new_bindings , array_inputevent[i] , array_actioncode[i] );

      g_free( array_actioncode ); g_free( array_inputevent ); /* not needed anymore */
    }
    else
    {
      /* no bindings defined */
      new_bindings = NULL;
    }
    /****************************************************************************/

    /* pick old bindings store and delete it */
    gtk_tree_model_get_iter( model , &iter , path );
    gtk_tree_model_get( model , &iter, DEVLIST_COL_BINDINGS , &old_bindings , -1 );
    if ( old_bindings != NULL )
      ed_bindings_store_delete( old_bindings );
    /* replace it with new one */
    gtk_list_store_set( GTK_LIST_STORE(model) , &iter , DEVLIST_COL_BINDINGS , new_bindings , -1 );
    /* everything done! */
  }
  gtk_widget_destroy( GTK_WIDGET(bindings_win) );
}


#define action_store_add(x) { gtk_list_store_append(action_store,&iter); gtk_list_store_set(action_store,&iter,0,player_actions[x].desc,1,x,-1); }

static void
cfg_ui_bindings_show ( ed_device_t * dev , GtkTreeRowReference * rowref )
{
  GtkWidget *bindings_win;
  GdkGeometry bindings_win_hints;
  GtkWidget *bindings_vbox;
  GtkWidget *bindings_info_table, *bindings_info_frame;
  GtkWidget *bindings_info_label_name_p, *bindings_info_label_name_c;
  GtkWidget *bindings_info_label_file_p, *bindings_info_label_file_c;
  GtkWidget *bindings_info_label_phys_p, *bindings_info_label_phys_c;
  GtkWidget *bindings_bind_frame, *bindings_bind_table, *bindings_bind_table_sw;
  GtkWidget *bindings_bbar_hbbox, *bindings_bbar_bt_cancel, *bindings_bbar_bt_ok;
  GtkListStore *action_store;
  GtkTreeIter iter;
  static gboolean style_added = FALSE;

  if ( !style_added )
  {
    /* this is used by trigger dialogs, calling it once is enough */
    gtk_rc_parse_string( "style \"noaaborder\" { GtkDialog::action-area-border = 0 }\n"
                         "widget \"trigger_dlg\" style \"noaaborder\"\n" );
    style_added = TRUE;
  }

  bindings_win = gtk_window_new( GTK_WINDOW_TOPLEVEL );
  gtk_window_set_type_hint( GTK_WINDOW(bindings_win), GDK_WINDOW_TYPE_HINT_DIALOG );
  gtk_window_set_position( GTK_WINDOW(bindings_win), GTK_WIN_POS_CENTER );
  gtk_window_set_transient_for( GTK_WINDOW(bindings_win), GTK_WINDOW(cfg_win) );
  gtk_window_set_modal( GTK_WINDOW(bindings_win) , TRUE );
  gtk_window_set_title( GTK_WINDOW(bindings_win), _("EvDev-Plug - Bindings Configuration") );
  gtk_container_set_border_width( GTK_CONTAINER(bindings_win), 10 );
  bindings_win_hints.min_width = 400;
  bindings_win_hints.min_height = 400;
  gtk_window_set_geometry_hints( GTK_WINDOW(bindings_win) , GTK_WIDGET(bindings_win) ,
                                 &bindings_win_hints , GDK_HINT_MIN_SIZE );

  g_object_set_data( G_OBJECT(bindings_win) , "rowref" , rowref );
  g_object_set_data( G_OBJECT(bindings_win) , "trigger-win" , NULL );

  bindings_vbox = gtk_vbox_new( FALSE , 4 );
  gtk_container_add( GTK_CONTAINER(bindings_win) , bindings_vbox );

  /* action combobox model
     column 0 -> action desc
     column 1 -> action code */
  action_store = gtk_list_store_new( 2 , G_TYPE_STRING , G_TYPE_INT );
  action_store_add( ED_ACTION_PB_PLAY );
  action_store_add( ED_ACTION_PB_STOP );
  action_store_add( ED_ACTION_PB_PAUSE );
  action_store_add( ED_ACTION_PB_PREV );
  action_store_add( ED_ACTION_PB_NEXT );
  action_store_add( ED_ACTION_PB_EJECT );
  action_store_add( ED_ACTION_PL_REPEAT );
  action_store_add( ED_ACTION_PL_SHUFFLE );
  action_store_add( ED_ACTION_VOL_UP5 );
  action_store_add( ED_ACTION_VOL_DOWN5 );
  action_store_add( ED_ACTION_VOL_UP10 );
  action_store_add( ED_ACTION_VOL_DOWN10 );
  action_store_add( ED_ACTION_VOL_MUTE );
  action_store_add( ED_ACTION_WIN_MAIN );
  action_store_add( ED_ACTION_WIN_PLAYLIST );
  action_store_add( ED_ACTION_WIN_EQUALIZER );
  action_store_add( ED_ACTION_WIN_JTF );
  g_object_set_data_full( G_OBJECT(bindings_win) , "action_store" , action_store , g_object_unref );

  /* info table */
  bindings_info_table = gtk_table_new( 3 , 2 , FALSE );
  gtk_container_set_border_width( GTK_CONTAINER(bindings_info_table), 4 );
  bindings_info_label_name_p = gtk_label_new( "" );
  gtk_label_set_markup( GTK_LABEL(bindings_info_label_name_p) , _("<b>Name: </b>") );
  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_name_p) , 0 , 0.5 );
  bindings_info_label_name_c = gtk_label_new( dev->info->name );
  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_name_c) , 0 , 0.5 );
  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_name_p , 0 , 1 , 0 , 1 ,
                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_name_c , 1 , 2 , 0 , 1 ,
                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
  bindings_info_label_file_p = gtk_label_new( "" );
  gtk_label_set_markup( GTK_LABEL(bindings_info_label_file_p) , _("<b>Filename: </b>") );
  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_file_p) , 0 , 0.5 );
  bindings_info_label_file_c = gtk_label_new( dev->info->filename );
  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_file_c) , 0 , 0.5 );
  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_file_p , 0 , 1 , 1 , 2 ,
                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_file_c , 1 , 2 , 1 , 2 ,
                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
  bindings_info_label_phys_p = gtk_label_new( "" );
  gtk_label_set_markup( GTK_LABEL(bindings_info_label_phys_p) , _("<b>Phys.Address: </b>") );
  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_phys_p) , 0 , 0.5 );
  bindings_info_label_phys_c = gtk_label_new( dev->info->phys );
  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_phys_c) , 0 , 0.5 );
  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_phys_p , 0 , 1 , 2 , 3 ,
                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_phys_c , 1 , 2 , 2 , 3 ,
                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
  bindings_info_frame = gtk_frame_new( NULL );
  gtk_container_add( GTK_CONTAINER(bindings_info_frame) , bindings_info_table );
  gtk_box_pack_start( GTK_BOX(bindings_vbox) , bindings_info_frame , FALSE , FALSE , 0 );

  /* bindings boxlist */
  bindings_bind_table = gtk_table_new( 1 , BINDLIST_NUMCOLS , FALSE );
  cfg_bindbox_new_empty_row( GTK_TABLE(bindings_bind_table) , bindings_win , FALSE );
  cfg_bindbox_populate( bindings_bind_table , bindings_win , dev->info->bindings );
  bindings_bind_table_sw = gtk_scrolled_window_new( NULL , NULL );
  gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(bindings_bind_table_sw) ,
                                  GTK_POLICY_NEVER , GTK_POLICY_ALWAYS );
  bindings_bind_frame = gtk_frame_new( NULL );
  gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW(bindings_bind_table_sw) ,
                                         bindings_bind_table );
  gtk_container_add( GTK_CONTAINER(bindings_bind_frame) , bindings_bind_table_sw );
  gtk_box_pack_start( GTK_BOX(bindings_vbox) , bindings_bind_frame , TRUE , TRUE , 0 );
  g_object_set_data( G_OBJECT(bindings_win) , "table" , bindings_bind_table );
  g_object_set_data( G_OBJECT(bindings_win) , "tablesw" , bindings_bind_table_sw );

  gtk_box_pack_start( GTK_BOX(bindings_vbox) , gtk_hseparator_new() , FALSE , FALSE , 0 );

  /* button bar */
  bindings_bbar_hbbox = gtk_hbutton_box_new();
  gtk_button_box_set_layout( GTK_BUTTON_BOX(bindings_bbar_hbbox) , GTK_BUTTONBOX_START );
  bindings_bbar_bt_cancel = gtk_button_new_from_stock( GTK_STOCK_CANCEL );
  bindings_bbar_bt_ok = gtk_button_new_from_stock( GTK_STOCK_OK );
  gtk_container_add( GTK_CONTAINER(bindings_bbar_hbbox) , bindings_bbar_bt_cancel );
  gtk_container_add( GTK_CONTAINER(bindings_bbar_hbbox) , bindings_bbar_bt_ok );
  gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(bindings_bbar_hbbox) , bindings_bbar_bt_cancel , TRUE );
  gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(bindings_bbar_hbbox) , bindings_bbar_bt_ok , TRUE );
  gtk_box_pack_start( GTK_BOX(bindings_vbox) , bindings_bbar_hbbox , FALSE , FALSE , 0 );

  /* activate device listening */
  dev->iochan_sid = g_io_add_watch( dev->iochan , G_IO_IN ,
    (GIOFunc)cfg_bindbox_assign_binding_input_func , bindings_win );

  /* signals */
  g_signal_connect( G_OBJECT(bindings_win) , "destroy" ,
                    G_CALLBACK(cfg_bindings_cb_destroy) , dev );
  g_signal_connect_swapped( G_OBJECT(bindings_bbar_bt_cancel) , "clicked" ,
                            G_CALLBACK(gtk_widget_destroy) , bindings_win );
  g_signal_connect_swapped( G_OBJECT(bindings_bbar_bt_ok) , "clicked" ,
                            G_CALLBACK(cfg_bindings_cb_commit) , bindings_win );

  gtk_widget_show_all( bindings_win );
}



/* about box */
void
ed_ui_about_show( void )
{
  static GtkWidget *about_win = NULL;
  GtkWidget *about_vbox;
  GtkWidget *logoandinfo_vbox;
  GtkWidget *info_tv, *info_tv_sw, *info_tv_frame;
  GtkWidget *bbar_bbox, *bbar_bt_ok;
  GtkTextBuffer *info_tb;
  GdkGeometry abount_win_hints;
  gchar *info_tb_content = NULL;

  if ( about_win != NULL )
  {
    gtk_window_present( GTK_WINDOW(about_win) );
    return;
  }

  about_win = gtk_window_new( GTK_WINDOW_TOPLEVEL );
  gtk_window_set_type_hint( GTK_WINDOW(about_win), GDK_WINDOW_TYPE_HINT_DIALOG );
  gtk_window_set_position( GTK_WINDOW(about_win), GTK_WIN_POS_CENTER );
  gtk_window_set_title( GTK_WINDOW(about_win), _("EvDev-Plug - about") );
  abount_win_hints.min_width = 420;
  abount_win_hints.min_height = 200;
  gtk_window_set_geometry_hints( GTK_WINDOW(about_win) , GTK_WIDGET(about_win) ,
                                 &abount_win_hints , GDK_HINT_MIN_SIZE );
  /* gtk_window_set_resizable( GTK_WINDOW(about_win) , FALSE ); */
  gtk_container_set_border_width( GTK_CONTAINER(about_win) , 10 );
  g_signal_connect( G_OBJECT(about_win) , "destroy" , G_CALLBACK(gtk_widget_destroyed) , &about_win );

  about_vbox = gtk_vbox_new( FALSE , 0 );
  gtk_container_add( GTK_CONTAINER(about_win) , about_vbox );

  logoandinfo_vbox = gtk_vbox_new( TRUE , 2 );

  /* TODO make a logo or make someone do it! :)
  logo_pixbuf = gdk_pixbuf_new_from_xpm_data( (const gchar **)evdev_plug_logo_xpm );
  logo_image = gtk_image_new_from_pixbuf( logo_pixbuf );
  g_object_unref( logo_pixbuf );

  logo_frame = gtk_frame_new( NULL );
  gtk_container_add( GTK_CONTAINER(logo_frame) , logo_image );
  gtk_box_pack_start( GTK_BOX(logoandinfo_vbox) , logo_frame , TRUE , TRUE , 0 );  */

  info_tv = gtk_text_view_new();
  info_tb = gtk_text_view_get_buffer( GTK_TEXT_VIEW(info_tv) );
  gtk_text_view_set_editable( GTK_TEXT_VIEW(info_tv) , FALSE );
  gtk_text_view_set_cursor_visible( GTK_TEXT_VIEW(info_tv) , FALSE );
  gtk_text_view_set_justification( GTK_TEXT_VIEW(info_tv) , GTK_JUSTIFY_LEFT );
  gtk_text_view_set_left_margin( GTK_TEXT_VIEW(info_tv) , 10 );

  info_tb_content = g_strjoin( NULL , "\nEvDev-Plug " , ED_VERSION_PLUGIN ,
                               _("\nplayer remote control via event devices\n"
                                 "http://www.develia.org/projects.php?p=audacious#evdevplug\n\n"
                                 "written by Giacomo Lozito\n") ,
                              "< james@develia.org >\n\n" , NULL );
  gtk_text_buffer_set_text( info_tb , info_tb_content , -1 );
  g_free( info_tb_content );

  info_tv_sw = gtk_scrolled_window_new( NULL , NULL );
  gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(info_tv_sw) ,
                                  GTK_POLICY_NEVER , GTK_POLICY_ALWAYS );
  gtk_container_add( GTK_CONTAINER(info_tv_sw) , info_tv );
  info_tv_frame = gtk_frame_new( NULL );
  gtk_container_add( GTK_CONTAINER(info_tv_frame) , info_tv_sw );
  gtk_box_pack_start( GTK_BOX(logoandinfo_vbox) , info_tv_frame , TRUE , TRUE , 0 );

  gtk_box_pack_start( GTK_BOX(about_vbox) , logoandinfo_vbox , TRUE , TRUE , 0 );

  /* horizontal separator and buttons */
  gtk_box_pack_start( GTK_BOX(about_vbox) , gtk_hseparator_new() , FALSE , FALSE , 4 );
  bbar_bbox = gtk_hbutton_box_new();
  gtk_button_box_set_layout( GTK_BUTTON_BOX(bbar_bbox) , GTK_BUTTONBOX_END );
  bbar_bt_ok = gtk_button_new_from_stock( GTK_STOCK_OK );
  g_signal_connect_swapped( G_OBJECT(bbar_bt_ok) , "clicked" ,
                            G_CALLBACK(gtk_widget_destroy) , about_win );
  gtk_container_add( GTK_CONTAINER(bbar_bbox) , bbar_bt_ok );
  gtk_box_pack_start( GTK_BOX(about_vbox) , bbar_bbox , FALSE , FALSE , 0 );

  gtk_widget_show_all( about_win );
}



/* message box */
void
ed_ui_message_show ( gchar * title , gchar * message , gpointer parent_win_gp )
{
  GtkWidget *message_win;
  GtkWindow *parent_win = NULL;

  if (( parent_win_gp != NULL ) && ( GTK_WIDGET_TOPLEVEL(GTK_WIDGET(parent_win_gp)) ))
    parent_win = GTK_WINDOW(parent_win_gp);

  message_win = gtk_message_dialog_new(
                  parent_win ,
                  ( parent_win != NULL ? GTK_DIALOG_DESTROY_WITH_PARENT : 0 ) ,
                  GTK_MESSAGE_INFO , GTK_BUTTONS_CLOSE , message );
  gtk_window_set_title( GTK_WINDOW(message_win) , title );

  gtk_dialog_run( GTK_DIALOG(message_win) );
  gtk_widget_destroy( message_win );
}