/*
 * bind.c --
 *
 * This file implements the bind command
 *
 * Copyright (c) 2001 - 2002 P.G. Baum
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */

/*
   History:
        12: added detail for key and button event
        11: converted to gnoclPercentSubstAndEval
            added focus{In,Out}
   2001-03: Begin of developement
 */

#include "gnocl.h"
#include "canvas.h"
#include <gtk/gtk.h>
#include <string.h>
#include <assert.h>

/* 
#define GNOCL_PRINT_DEBUG 
*/

/* 
   Gtk knows only signals, but we can bind also different scripts on
   the same signal but different details, so we have for every signal 
   a GArray of scripts distinguishable by different details
*/
enum { DETAIL_DEFAULT = -1 };

typedef struct
{
   int  detail;
   char *detailStr;
   char *script;
} DetailScript;

typedef struct
{
   Tcl_Interp *interp;
   GArray     *scripts;
   gint       id;
} GnoclBindStruct;

typedef struct 
{
   GtkObject       *obj;
   char            *signal;
   GnoclBindStruct *bs;
} WidgetBinding;

typedef struct
{
   GdkEventType event;
   int          detail;
   char         *detailStr;
   char         *script;
   Tcl_Interp   *interp;
} CanvasItemEvent;

typedef struct
{
   GArray *events;
   gint   id;
}EventsAndHandlerId;

   /* pevents must be a pointer, because it is saved for use in 
      eventFunc, but EventsAndHandlerId can move, if canvasItemEvents
      is resized */
typedef struct
{
   EventsAndHandlerId *pevents;  
   GtkObject *obj; 
} EventsAndObj;


static GArray *canvasItemEvents = NULL;
static GArray *widgetBindings = NULL;

static void dump( void )
{
   int k, m;
   if( canvasItemEvents == NULL )
   {
      printf( "canvasItems empty\n" );
      return;
   }
   printf( "canvasItems len: %d\n", canvasItemEvents->len );
   for( k = 0; k < canvasItemEvents->len; ++k )
   {
      EventsAndObj *eao = &g_array_index( canvasItemEvents, 
            EventsAndObj, k );
      printf( "%d: obj: %p\n", k, eao->obj );
      for( m = 0; m < eao->pevents->events->len; ++m )
      {
         CanvasItemEvent *ev = &g_array_index( eao->pevents->events, 
               CanvasItemEvent, m );
         printf( "   %d event: %d, \"%s\"\n", m, ev->event, ev->script );
      }
   }
}

static EventsAndHandlerId *newCanvasItemEvents( GtkObject *obj )
{
   EventsAndObj eao;
   EventsAndObj *p;

   /* assert( getCanvasItemEvents( obj, 0 ) == NULL ); */

   if( canvasItemEvents == NULL )
      canvasItemEvents = g_array_new( 0, 0, sizeof( EventsAndObj ) );
   
   eao.obj = obj;
   eao.pevents = g_new( EventsAndHandlerId, 1 );
   canvasItemEvents = g_array_append_val( canvasItemEvents, eao );

   p = &g_array_index( canvasItemEvents, EventsAndObj, 
         canvasItemEvents->len - 1 );

   /* assert( getCanvasItemEvents( obj, 0 ) == p->pevents ); */

   return p->pevents;
}

static int deleteCanvasItemEvent( EventsAndHandlerId *evhi )
{
   if( canvasItemEvents != NULL )
   {
      int k;
      for( k = 0; k < canvasItemEvents->len; ++k )
      {
         EventsAndObj *eao = &g_array_index( canvasItemEvents, 
               EventsAndObj, k );
         if( eao->pevents == evhi )
         {
            g_array_free( eao->pevents->events, 1 );
            /* gtk_signal_disconnect( obj, eao->pevents->handlerId ); */
            g_free( eao->pevents );
            canvasItemEvents = g_array_remove_index_fast( 
                  canvasItemEvents, k );
            return 1;
         }
      }
   }
   return 0;
}

static void destroyItemBinding( gpointer data )
{
   EventsAndHandlerId *evhi = (EventsAndHandlerId *)data;
   int k;

   for( k = 0; k < evhi->events->len; ++k )
   {
      CanvasItemEvent *pev = &g_array_index( evhi->events, 
            CanvasItemEvent, k );
      #ifdef GNOCL_PRINT_DEBUG
      if( pev->script )
         printf( "in destroyItemBinding \"%s\"\n", pev->script );
      #endif

      g_free( pev->script );
   }
   deleteCanvasItemEvent( evhi );
}

static void eventFunc( GtkWidget *widget, GdkEvent *event, 
      gpointer data)
{
   static Gnocl_PercSubst ps[] = {
      { 'x', GNOCL_INT },  
      { 'y', GNOCL_INT }, 
      { 'b', GNOCL_INT }, 
      { 0 }
   };

   GArray *array = ((EventsAndHandlerId *)data)->events;
   CanvasItemEvent *ev;
   int k;

   for( k = 0; k < array->len; ++k )
   {
      ev = &g_array_index( array, CanvasItemEvent, k );
      if( ev->event == event->type )
      {
         /*
         #ifdef GNOCL_PRINT_DEBUG
         printf( "executing: %p \"%s\"\n", data, ev->script );
         #endif
         */
         switch( event->type )
         {
            case GDK_ENTER_NOTIFY:
            case GDK_LEAVE_NOTIFY:
                     ps[0].val.i = event->crossing.x;
                     ps[1].val.i = event->crossing.y;
                     break;
            case GDK_MOTION_NOTIFY:
                     ps[0].val.i = event->motion.x;
                     ps[1].val.i = event->motion.y;
                     break;
            case GDK_BUTTON_PRESS:
            case GDK_BUTTON_RELEASE:
                     if( ev->detail != DETAIL_DEFAULT
                           && ev->detail != event->button.button )
                        continue;       /* test next event */

                     ps[0].val.i = event->button.x;
                     ps[1].val.i = event->button.y;
                     ps[2].val.i = event->button.button;
                     break;
            case GDK_KEY_PRESS:
            case GDK_KEY_RELEASE:
            default: assert( 0 );
         }
         gnoclPercentSubstAndEval( ev->interp, ps, ev->script );
      }
   }
   /*
   #ifdef GNOCL_PRINT_DEBUG
   if( k == array->len )
   {
      printf( "event %d not found\n", event->type );
      return;
   }
   #endif
   */
}



static EventsAndHandlerId *getCanvasItemEvents( GtkObject *obj, int new )
{
   if( canvasItemEvents != NULL )
   {
      int k;
      for( k = 0; k < canvasItemEvents->len; ++k )
      {
         EventsAndObj *eao = &g_array_index( canvasItemEvents, 
               EventsAndObj, k );
         if( eao->obj == obj )
            return eao->pevents;
      }
   }
   if( new )
   {
      EventsAndHandlerId *evhi = newCanvasItemEvents( obj );
      evhi->events = g_array_new( 0, 0, sizeof( CanvasItemEvent ) );
      /*
      evhi->id = gtk_signal_connect( object, "event", 
            eventFunc, evhi );
      gtk_signal_connect_full( );
      */
      evhi->id = gtk_signal_connect_full( obj, "event", eventFunc, 
            NULL, evhi, destroyItemBinding, 0, 0 ); 
      return evhi;
   }

   return NULL;
}


static CanvasItemEvent *findItemBindStruct( EventsAndHandlerId *evhi, 
      GdkEventType event, int detail )
{
   int k;
   for( k = 0; k < evhi->events->len; ++k )
   {
      CanvasItemEvent *pev = &g_array_index( evhi->events, 
            CanvasItemEvent, k );
      if( pev->event == event && pev->detail == detail )
         return pev;
   }
   return NULL;
}
static int deleteItemBindStruct( EventsAndHandlerId *evhi, 
      GdkEventType event, GtkObject *object )
{
   int k;
   for( k = 0; k < evhi->events->len; ++k )
   {
      CanvasItemEvent *pev = &g_array_index( evhi->events, 
            CanvasItemEvent, k );
      if( pev->event == event )
      {
         evhi->events = g_array_remove_index_fast( evhi->events, k );
         if( evhi->events->len == 0 )
         {
            gtk_signal_disconnect( object, evhi->id );
            deleteCanvasItemEvent( evhi );
         }
         return 1;
      }
   }
   return 0;
}

static int insertItemBindStruct( EventsAndHandlerId *evhi, 
      CanvasItemEvent event )
{
   assert( findItemBindStruct( evhi, event.event, event.detail ) == NULL );
   evhi->events = g_array_append_val( evhi->events, event );
   return 0;
}



#if 0
static int setCanvasItemEvents( GtkObject *obj, EventsAndHandlerId *events )
{
   EventsAndHandlerId *eai;
   int k;

   if( canvasItemEvents == NULL )
      canvasItemEvents = g_array_new( 0, 0, sizeof( EventsAndObj ) );
   
   /* test, if obj exist and change the event member */
   eai = getCanvasItemEvents( GtkObject *obj )
   if( eai != NULL )
   {
      eai->events = events;
   }
   else
   {
      /* obj does not exist: insert */
      EventsAndObj eao;
      eao.obj = obj;
      eao.events = events;
      canvasItemEvents = g_array_append_val( canvasItemEvents, eao );
   }

   return 0;
}
#endif

static void focusFunc( GtkWidget *widget, GdkEventFocus *event,
      gpointer data )
{
   static Gnocl_PercSubst ps[] = {
      { 'f', GNOCL_INT },       /* boolean: true: got focus */
      { 0 }
   };

   GnoclBindStruct *p = (GnoclBindStruct *)data;
   DetailScript *ds = &g_array_index( p->scripts, DetailScript, 0 );
   assert( p->scripts->len == 1 );

   ps[0].val.i = event->in;


   gnoclPercentSubstAndEval( p->interp, ps, ds->script );
}
static void crossingFunc( GtkWidget *widget, GdkEventCrossing *event,
      gpointer data )
{
   static Gnocl_PercSubst ps[] = {
      { 'x', GNOCL_INT },  
      { 'y', GNOCL_INT }, 
      { 0 }
   };

   GnoclBindStruct *p = (GnoclBindStruct *)data;
   DetailScript *ds = &g_array_index( p->scripts, DetailScript, 0 );
   assert( p->scripts->len == 1 );

   ps[0].val.i = event->x;
   ps[1].val.i = event->y;

   gnoclPercentSubstAndEval( p->interp, ps, ds->script );
}
static void motionFunc( GtkWidget *widget, GdkEventMotion *event,
      gpointer data )
{
   static Gnocl_PercSubst ps[] = {
      { 'x', GNOCL_INT },  
      { 'y', GNOCL_INT }, 
      { 0 }
   };

   GnoclBindStruct *p = (GnoclBindStruct *)data;
   DetailScript *ds = &g_array_index( p->scripts, DetailScript, 0 );
   assert( p->scripts->len == 1 );

   ps[0].val.i = event->x;
   ps[1].val.i = event->y;

   gnoclPercentSubstAndEval( p->interp, ps, ds->script );
}

static void buttonFunc( GtkWidget *widget, GdkEventButton *event,
      gpointer data )
{
   static Gnocl_PercSubst ps[] = {
      { 'x', GNOCL_INT },  
      { 'y', GNOCL_INT }, 
      { 'b', GNOCL_INT }, 
      { 0 }
   };

   GnoclBindStruct *p = (GnoclBindStruct *)data;
   int k;

   ps[0].val.i = event->x;
   ps[1].val.i = event->y;
   ps[2].val.i = event->button;

   for( k = 0; k < p->scripts->len; ++k )
   {
      DetailScript *ds = &g_array_index( p->scripts, DetailScript, k );
      if( ds->detail == DETAIL_DEFAULT || ds->detail == event->button )
         gnoclPercentSubstAndEval( p->interp, ps, ds->script );
   }
}

static void keyFunc( GtkWidget *widget, GdkEventKey *event,
      gpointer data )
{
   static Gnocl_PercSubst ps[] = {
      { 'k', GNOCL_INT },
      { 'K', GNOCL_STRING },
      { 'A', GNOCL_STRING },
      { 's', GNOCL_INT },
      { 0 }
   };

   GnoclBindStruct *p = (GnoclBindStruct *)data;
   int k;
   char buf[] = "\\ ";
   int  eval = 0;
   int  firstEval = 1;

   /* same hash table as in parseSignalArg but the other way round */
   static GHashTable *keysyms = NULL;

   if ( keysyms == NULL )
   {
      struct { const char *name; guint sym; } syms[] = {
            #include "keysyms.h"
            };
      int k;
      keysyms = g_hash_table_new( g_direct_hash, g_direct_equal );
      for( k = 0; k < sizeof( syms ) / sizeof( *syms ); ++k )
         g_hash_table_insert( keysyms, (gpointer)syms[k].sym,
               (gpointer)syms[k].name );
   }

   for( k = 0; k < p->scripts->len; ++k )
   {
      DetailScript *ds = &g_array_index( p->scripts, DetailScript, k );
      if( ds->detailStr == NULL )
      {
         if( ds->detail == DETAIL_DEFAULT 
               || ds->detail == event->keyval )
            eval = 1;
      } 
      else if( event->string != NULL && *ds->detailStr == *event->string
            && strcmp( ds->detailStr, event->string ) == 0 )
         eval = 1;
      if( eval )
      {
         eval = 0;
         /* do g_hash_table_lookup only if the script is realy and the first
            time evaluated */
         if( firstEval )
         {
            firstEval = 0;
            ps[0].val.i = event->keyval;
            ps[1].val.str = g_hash_table_lookup( keysyms, 
                  (gpointer)event->keyval );
            /* escape characters with a special meaning for Tcl */
            if( event->length == 1 && strchr( "{}[]$\"\\", *event->string ) )
            {
               buf[1] = *event->string;
               ps[2].val.str = buf;
            }
            else
               ps[2].val.str = event->string;

            /* TODO: function for converting state to list */
            ps[3].val.i = event->state;  
         }
         /* in the upper if: "*detailStr == *string" is only for speedup */
         gnoclPercentSubstAndEval( p->interp, ps, ds->script );
      }
   }
}

static int insertBindStruct( GtkObject *obj, 
      const char *signal, GnoclBindStruct *bs )
{
   WidgetBinding wb;
   wb.obj = obj;
   wb.signal = g_memdup( signal, strlen( signal ) + 1 );
   wb.bs = bs;
   if( widgetBindings == NULL )
      widgetBindings = g_array_new( 0, 0, sizeof( WidgetBinding ) );
   
   widgetBindings = g_array_append_val( widgetBindings, wb );

   return 0;
}

static GnoclBindStruct *findBindStruct( GtkObject *obj, 
      const char *signal )
{
   if( widgetBindings != NULL )
   {
      int k;
      for( k = 0; k < widgetBindings->len; ++k )
      {
         WidgetBinding *wb = &g_array_index( widgetBindings, 
               WidgetBinding, k );
         if( wb->obj == obj && !strcmp( wb->signal, signal ) )
            return wb->bs;
      }
   }

   return NULL;
}

static int deleteBindStruct( GnoclBindStruct *bs )
{
   if( widgetBindings != NULL )
   {
      int k;
      for( k = 0; k < widgetBindings->len; ++k )
      {
         WidgetBinding *wb = &g_array_index( widgetBindings, 
               WidgetBinding, k );
         if( wb->bs == bs )
         {
            g_free( wb->signal );
            g_array_remove_index_fast( widgetBindings, k );
            return 1;
         }
      }
   }

   return 0;
}

static void destroyBinding( gpointer data )
{
   GnoclBindStruct *bs = (GnoclBindStruct *)data;
   int k;

   /* printf( "in destroyBinding %p\n", bs ); */
   #ifdef GNOCL_PRINT_DEBUG
   if( bs->script )
      printf( "in destroyBinding \"%s\"\n", bs->script );
   #endif
   for( k = 0; k < bs->scripts->len; ++k )
   {
      DetailScript *ds = &g_array_index( bs->scripts, DetailScript, k );
      g_free( ds->script );
   }
   deleteBindStruct( bs );
}

static int parseSignalArg( Tcl_Interp *interp, Tcl_Obj *obj,
      const char **signal, int *detail, char **detailStr )
{
   int no;
   Tcl_Obj *tp;

   if( Tcl_ListObjLength( interp, obj, &no ) != TCL_OK 
         || no < 1 || no > 2 )
   {
      Tcl_SetResult( interp, 
            "signal-list must be proper list with at most 2 elements", 
            TCL_STATIC );
      return TCL_ERROR;
   }

   if( Tcl_ListObjIndex( interp, obj, 0, &tp ) != TCL_OK )
      return TCL_ERROR;
   *signal = Tcl_GetString( tp );
   *detail = DETAIL_DEFAULT;
   *detailStr = NULL;

   if( no > 1 )
   {
      if( Tcl_ListObjIndex( interp, obj, 1, &tp ) != TCL_OK )
         return TCL_ERROR;
      if( strcmp( *signal, "button" ) == 0
            || strcmp( *signal, "buttonPress" ) == 0 
            || strcmp( *signal, "buttonRelease" ) == 0 )
      {
         if( Tcl_GetIntFromObj( interp, tp, detail ) != TCL_OK )
            return TCL_ERROR;
         if( detail <= 0 )
         {
            Tcl_SetResult( interp, 
                  "button number must be strictly positive.", TCL_STATIC );
            return TCL_ERROR;
         }
         *detailStr = NULL;
      }
      else if( strcmp( *signal, "key" ) == 0
            || strcmp( *signal, "keyPress" ) == 0 
            || strcmp( *signal, "keyRelease" ) == 0 )
      {
         char *p = Tcl_GetString( tp );
         if( *p == '#' )        /* use keysyms */
         {
            /* initialize only if needed */
            static GHashTable *keysyms = NULL;
            gpointer symp;

            /* same hash table as in keyFunc but the other way round */
            if ( keysyms == NULL )
            {
               struct { const char *name; guint sym; } syms[] = {
                     #include "keysyms.h"
                     };
               int k;
               keysyms = g_hash_table_new( g_str_hash, g_str_equal );
               for( k = 0; k < sizeof( syms ) / sizeof( *syms ); ++k )
                  g_hash_table_insert( keysyms, (gpointer)syms[k].name, 
                        (gpointer)syms[k].sym );
            }
            symp = g_hash_table_lookup( keysyms, p + 1 );
            if( symp == NULL )
            {
               Tcl_AppendResult( interp, "unknown keysym\"", p + 1,
                     "\"", NULL );
               return TCL_ERROR;
            }
            *detailStr = NULL;
            *detail = (int)symp;
            
         }
         else
         {
            *detailStr = g_memdup( p, strlen( p ) + 1 );
            *detail = DETAIL_DEFAULT;
         }
      }
      else
      {
         Tcl_AppendResult( interp, "for signal\"", *signal, 
               "\" no detail valid", NULL );
         return TCL_ERROR;
      }
   }
 
   return TCL_OK;
}

int gnoclBindCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   const char  *name;
   GtkObject   *object;
   GtkWidget   *widget;
   const char  *script;
   const char  *signal;
   int         detail;
   char        *detailStr;
   int         scriptLen;
   int         idx;
   int         isCanvasItem = 0;
   GnoclBindStruct *bs;
   /*
   int signalLen;
   buffer[1024]; look for signal > 1000!!! 
   */

   typedef struct 
   {
      const char *name;
      const char *native;
      GtkSignalFunc func;
      GdkEventType  eventType;
   } EventStruct;
   EventStruct events[] = {
      { "enter", "enter_notify_event", crossingFunc, GDK_ENTER_NOTIFY },
      { "focusIn", "focus_in_event", focusFunc, GDK_FOCUS_CHANGE },
      { "focusOut", "focus_out_event", focusFunc, GDK_FOCUS_CHANGE },
      { "leave", "leave_notify_event", crossingFunc, GDK_LEAVE_NOTIFY },
      { "motion", "motion_notify_event", motionFunc, GDK_MOTION_NOTIFY },
      { "button", "button_press_event", buttonFunc, GDK_BUTTON_PRESS },
      { "buttonPress", "button_press_event", buttonFunc, GDK_BUTTON_PRESS },
      { "buttonRelease", "button_release_event", buttonFunc, 
            GDK_BUTTON_RELEASE },
      { "key", "key_press_event", keyFunc, GDK_KEY_PRESS },
      { "keyPress", "key_press_event", keyFunc, GDK_KEY_PRESS },
      { "keyRelease", "key_release_event", keyFunc, GDK_KEY_RELEASE },

      /* { "event", NULL, eventFunc }, */
      { NULL, NULL }
   };
   
   if( objc == 2 && strcmp( "dump", Tcl_GetString( objv[1] ) ) == 0 )
   {
      dump();
      return TCL_OK;
   }
   /* bind widget signal script */
   if( objc != 4 )
   {
      Tcl_WrongNumArgs( interp, 1, objv, "widget signal script" );
      return TCL_ERROR;
   }

   name = gnoclGetString( objv[1] );
   if( parseSignalArg( interp, objv[2], &signal, &detail, &detailStr ) 
         != TCL_OK )
      return TCL_ERROR;

   script = gnoclGetStringFromObj( objv[3], &scriptLen );

   /* 
      name[0] == '_' -> widget
      isdigit( name[0] ) -> canvas item
      else -> tag for canvas item
   */
   /* variable "widget" and "item" only because 
      GTK_OBJECT( NULL ) gives a warning: 
      Gtk-WARNING **: invalid cast from (NULL) pointer to `GtkObject 
   */
   widget = gnoclGetWidgetFromName( name, NULL );
   /* for text, list and canvas */
   if( widget != NULL && GTK_CHECK_TYPE( widget, GTK_TYPE_SCROLLED_WINDOW ) )
   {
      widget = GTK_BIN( widget )->child;
   }

   if( widget != NULL )
   {
      object = GTK_OBJECT( widget );
   }
   else
   {
      Gnocl_CanvasItemInfo *info = gnoclGetCanvasInfoFromName( name );
      if( info )
         object = GTK_OBJECT( info->item );
      isCanvasItem = 1;
   }

   if( object == NULL )
   {
      Tcl_AppendResult( interp, "\"", name, 
            "\" is not a window or canvas item.", (char *)NULL );
      return TCL_ERROR;
   }

   /* gtk_signal_query () */

   /* error in Tcl_GetIndexFromObjStruct 8.3.2 for cached values */
   {
      Tcl_Obj     *sigObj = Tcl_NewStringObj( signal, -1 );
      int ret = Tcl_GetIndexFromObjStruct( interp, sigObj, 
            (char **)&events[0].name, sizeof( EventStruct ), "signal", 
            TCL_EXACT, &idx );
      Tcl_DecrRefCount( sigObj );
      if( ret != TCL_OK )
         return TCL_ERROR;
   }

   if( isCanvasItem )
   {
      /* 
         it seems, that only the signal "event" is known for canvas items 
         therefor we map every signal to "event", remember the signal
         type and call in the signal function for "event" the 
         appropriate script
      */

      EventsAndHandlerId *evhi = getCanvasItemEvents( object, 1 );
      GdkEventType  event = events[idx].eventType;
      CanvasItemEvent *pev = findItemBindStruct( evhi, event, detail );
      if( pev == NULL )
      {
         if( scriptLen > 0 )
         {
            CanvasItemEvent ev;
            ev.event = event;
            ev.detail = detail;
            ev.detailStr = detailStr;
            ev.script = g_memdup( script, scriptLen + 1 );
            ev.interp = interp;
            insertItemBindStruct( evhi, ev );
         }
      }
      else
      {
         if( scriptLen > 0 )
         {
            /* replace script */
            g_free( pev->script );
            pev->script = g_memdup( script, scriptLen + 1 );
            assert( pev->interp = interp );
         }
         else
         {
            deleteItemBindStruct( evhi, event, object );
         }
      }
   }
   else
   {
      const char  *signal = events[idx].native;
      if( signal == NULL )
         signal = events[idx].name;
      
      bs = findBindStruct( object, signal );
      /* printf( "findBindStruct: %p\n", bs ); */
      if( bs == NULL )
      {
         if( scriptLen > 0 )
         {
            DetailScript ds = { detail, detailStr, 
                  g_memdup( script, scriptLen + 1 ) };
            bs = g_new( GnoclBindStruct, 1 );
            bs->interp = interp;
            bs->scripts = g_array_new( 0, 0, sizeof( DetailScript ) );
            bs->scripts = g_array_append_val( bs->scripts, ds );

            bs->id = gtk_signal_connect_full( object, signal, events[idx].func, 
                  NULL, bs, destroyBinding, 0, 0 ); 
                  /* FIXME: last parameters??? */
            insertBindStruct( object, signal, bs );
         }
      }
      else
      {
         if( scriptLen > 0 )
         {
            int k;
            for( k = 0; k < bs->scripts->len; ++k )
            {
               DetailScript *ds = &g_array_index( bs->scripts, 
                     DetailScript, k );
               if( ds->detail == detail && 
                     ( ( ds->detailStr == NULL && detailStr == NULL ) || 
                     ( ds->detailStr != NULL && detailStr != NULL &&
                     strcmp( ds->detailStr, detailStr ) == 0 ) ) )
               {
                  /* replace script */
                  assert( bs->interp == interp );
                  g_free( ds->script );
                  ds->script = g_memdup( script, scriptLen + 1 );
                  break;
               }
            }
            /* if same detail not found, append new signal */
            if( k == bs->scripts->len )
            {
               DetailScript ds = { detail, detailStr, 
                     g_memdup( script, scriptLen + 1 ) };
               bs->scripts = g_array_append_val( bs->scripts, ds );
            }
         }
         else
         {
            DetailScript *ds = &g_array_index( bs->scripts, DetailScript, 0 );
            g_free( ds->script );

            bs->scripts = g_array_remove_index( bs->scripts, 0 );
            /* deletion is handled automatically in destroyBinding */
            gtk_signal_disconnect( object, bs->id );
         }
      }
         
   }

   return TCL_OK;
}

