/*
 * dragAndDrop.c --
 *
 * This file implements drag and drop functions
 *
 * Copyright (c) 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:
   2002-01: Begin of developement
 */

#include "gnocl.h"
#include <string.h>
#include <assert.h>

typedef struct
{
   Tcl_Interp *interp;
   char *command;
   char *name;
} DragData;

static void dragDataReceived( GtkWidget *widget, GdkDragContext *context,
   gint x, gint y, GtkSelectionData *selectionData, guint info, 
   guint32 time, gpointer data )
{
   DragData *para = (DragData *)data;
   int      ret;
   gboolean del = 0;
   static Gnocl_PercSubst ps[] = {
      { 'd', GNOCL_OBJ },     /* 0 data */
      { 'l', GNOCL_INT },     /* 1 length of data */
      { 't', GNOCL_INT },     /* 2 time */
      { 'T', GNOCL_STRING },  /* 3 type */
      { 'w', GNOCL_STRING },  /* 4 widget */
      { 'x', GNOCL_INT },     /* 5 x */
      { 'y', GNOCL_INT },     /* 6 y */
      { 0 }
   };

   ps[0].val.obj = Tcl_NewByteArrayObj( selectionData->data, 
         selectionData->length );
   ps[1].val.i = selectionData->length;
   ps[2].val.i = time;
   ps[3].val.str = gdk_atom_name( selectionData->type );
   ps[4].val.str = para->name;
   ps[5].val.i = x;
   ps[6].val.i = y;

   /*
   printf( "selection: \"%s\" target: \"%s\" type: \"%s\" data: \"%s\"\n", 
         gdk_atom_name( selectionData->selection ),
         gdk_atom_name( selectionData->target ),
         gdk_atom_name( selectionData->type ),
         selectionData->data );
   */

   ret = gnoclPercentSubstAndEval( para->interp, ps, para->command );
   if( ret == TCL_OK )
   {
      /* delete drag data if tcl function returns "delete" */
      Tcl_Obj *res = Tcl_GetObjResult( para->interp );
      char *str = Tcl_GetString( res );
      if( strcmp( str, "delete" ) == 0 )
         del = 1;
   }
   gtk_drag_finish( context, ret == TCL_OK, del, time );

   g_free( (char *)ps[3].val.str );
}

static void dragBegin( GtkWidget *widget, GdkDragContext *context,
      gpointer data )
{
   /* TODO: set custom icon */
   /* DragData *para = (DragData *)data; */
   gtk_drag_set_icon_default( context );
}

static void dragDataGet( GtkWidget *widget, GdkDragContext *context,
   GtkSelectionData *selectionData, guint info, guint32 time, gpointer data )
{
   DragData *para = (DragData *)data;

   static Gnocl_PercSubst ps[] = {
      { 'T', GNOCL_OBJ },     /* 0 types of target */
      { 't', GNOCL_INT },     /* 1 time */
      { 'w', GNOCL_STRING },  /* 2 widget */
      { 0 }
   };

   gchar *name = gdk_atom_name( GPOINTER_TO_INT( selectionData->target ) );
   ps[0].val.obj = Tcl_NewStringObj( name, -1 );
   ps[1].val.i = time;
   ps[2].val.str = para->name;
   
   /*
      context->targets is a GList with all targets of the source.
      The first one, which is present in target is used and copied 
      to selectionData->target
   */

   if( gnoclPercentSubstAndEval( para->interp, ps, para->command ) == TCL_OK )
   {
      Tcl_Obj *res = Tcl_GetObjResult( para->interp );

      int len;
      unsigned char *pd = Tcl_GetByteArrayFromObj( res, &len );
      /* FIXME: format (number of bits in a unit) is 16, why?,
                what is a "unit"? */
      gtk_selection_data_set( selectionData, gdk_atom_intern( name, FALSE),
            16, (guchar *)pd, len );
   }
   g_free (name);
}

static int getButtonMask( Tcl_Interp *interp, Tcl_Obj *obj, 
      GdkModifierType *mask )
{
   GtkUpdateType types[] = { GDK_BUTTON1_MASK, GDK_BUTTON2_MASK,
         GDK_BUTTON3_MASK, GDK_BUTTON4_MASK, GDK_BUTTON5_MASK };

   int k, no;

   if( Tcl_ListObjLength( interp, obj, &no ) != TCL_OK ) 
      return TCL_ERROR;

   *mask = 0;
   for( k = 0; k < no; ++k )
   {
      Tcl_Obj *tp;
      int idx;
      if( Tcl_ListObjIndex( interp, obj, k, &tp ) != TCL_OK )
         return TCL_ERROR;
      if( Tcl_GetIntFromObj( interp, obj, &idx ) != TCL_OK
         || idx < 1 || idx > 5 )
      {
         Tcl_SetResult( interp, 
               "Button mask must be an integer between 1 and 5.", TCL_STATIC );
         return TCL_ERROR;
      }
      *mask |= types[idx-1];
   }

   return TCL_OK;
}

static int getDragAction( Tcl_Interp *interp, Tcl_Obj *obj, 
      GdkDragAction *act )
{
   char *actTxt[] = { "default", "copy", "move", "link", "private", 
         "ask", NULL };
   GtkUpdateType types[] = { GDK_ACTION_DEFAULT, GDK_ACTION_COPY,
         GDK_ACTION_MOVE, GDK_ACTION_LINK, GDK_ACTION_PRIVATE,
         GDK_ACTION_ASK };

   int k, no;

   if( Tcl_ListObjLength( interp, obj, &no ) != TCL_OK ) 
      return TCL_ERROR;

   *act = 0;
   for( k = 0; k < no; ++k )
   {
      Tcl_Obj *tp;
      int idx;
      if( Tcl_ListObjIndex( interp, obj, k, &tp ) != TCL_OK )
         return TCL_ERROR;
      if( Tcl_GetIndexFromObj( interp, obj, actTxt, "drag actions",
            TCL_EXACT, &idx ) != TCL_OK )
         return TCL_ERROR;
      *act |= types[idx];
   }

   return TCL_OK;
}



static int setDestination( Tcl_Interp *interp, int objc, 
      Tcl_Obj * const objv[], int setDestinationFlag )
{
   typedef struct 
   {
      Gnocl_ListBool actions;
      Gnocl_ListBool buttons;
   } DragOptions;
   const GnoclWidgetOptions sourceOptions[] = 
   {
      { "-actions", GNOCL_LIST, GNOCL_OFFSET( DragOptions, actions ) },
      { "-buttons", GNOCL_LIST, GNOCL_OFFSET( DragOptions, buttons ) },
      { NULL, 0, 0 }
   };
   const GnoclWidgetOptions destOptions[] = 
   {
      { "-actions", GNOCL_LIST, GNOCL_OFFSET( DragOptions, actions ) },
      { NULL, 0, 0 }
   };
   
   int            k, noTargets;
   GtkWidget      *widget;
   GtkTargetEntry *targets = NULL;
   int            ret = TCL_OK;
   DragData       *para = NULL;
   DragOptions    opt;
   GdkDragAction  action = GDK_ACTION_COPY;
   GdkModifierType buttonMask = GDK_BUTTON1_MASK;

   /* dnd setDestination widget targets command ?...? */
   if( objc < 5 )
   {
      Tcl_WrongNumArgs( interp, 2, objv, 
            "widget list-of-targets command ?option val ...?" );
      return TCL_ERROR;
   }
   memset( &opt, 0, sizeof( opt ) );
   if( gnoclParseAllWidgetOpts( interp, objc, objv, 4,
         setDestinationFlag ? destOptions : sourceOptions, 
         (char *)&opt ) != TCL_OK )
      return TCL_ERROR;

   if( opt.actions.changed )
   {
      if( getDragAction( interp, opt.actions.val, &action ) != TCL_OK )
         return TCL_ERROR;
   }
   if( opt.buttons.changed )
   {
      if( getButtonMask( interp, opt.actions.val, &buttonMask ) != TCL_OK )
         return TCL_ERROR;
   }

   widget = gnoclGetWidgetFromName( Tcl_GetString( objv[2] ), interp );
   if( widget == NULL )
      return TCL_ERROR;

   if( setDestinationFlag == 0 && GTK_WIDGET_NO_WINDOW( widget ) )
   {
      /* TODO? insert GtkEventBox?
               add gnocl::eventBox?
               -eventBox boolean in gnocl::box? */
      Tcl_SetResult( interp, 
            "The drag and drop source must have a window widget.\n"
            "Please use an additional gnocl::eventBox.", 
            TCL_STATIC );
      return TCL_ERROR;
   }


   if( Tcl_ListObjLength( interp, objv[3], &noTargets ) != TCL_OK )
      return TCL_ERROR;
   if( noTargets < 1 )
   {
      Tcl_SetResult( interp, 
            "List-of-targets must contain at least one element.", TCL_STATIC );
      return TCL_ERROR;
   }
   para = g_new( DragData, 1 );
   para->interp = interp;
   para->command = gnoclStringDup( objv[4] );
   para->name = gnoclStringDup( objv[2] );

   targets = g_new( GtkTargetEntry, noTargets );
   for( k = 0; k < noTargets; ++k )
   {
      Tcl_Obj *pobj;

      if( Tcl_ListObjIndex( interp, objv[3], k, &pobj ) != TCL_OK )
      {
         ret = TCL_ERROR;
         break;
      }

      targets[k].target = Tcl_GetString( pobj );
      /* TODO: GTK_TARGET_SAME_APP, GTK_TARGET_SAME_WIDGET */
      targets[k].flags = 0;  
      targets[k].info = k;
   }

   if( ret == TCL_OK )
   {
      /*
      GtkTargetList  *targetList = NULL;
      gtk_target_list_add_table( targetList, targets, noTargets ); 
      */
      if( setDestinationFlag )
      {
         gtk_drag_dest_set( widget, GTK_DEST_DEFAULT_ALL, targets, noTargets, 
               action ); /* GDK_ACTION_COPY */
         gtk_signal_connect( GTK_OBJECT( widget ), "drag_data_received",
               GTK_SIGNAL_FUNC( dragDataReceived ), para );
      }
      else
      {
         /* TODO: -dragBegin -dragEnd */
         gtk_drag_source_set( widget, buttonMask, targets, 
               noTargets, action ); /* GDK_ACTION_COPY | GDK_ACTION_MOVE */
         gtk_signal_connect( GTK_OBJECT( widget ), "drag_begin",
               GTK_SIGNAL_FUNC( dragBegin ), para );
         /*
         gtk_signal_connect( GTK_OBJECT( widget ), "drag_end",
               GTK_SIGNAL_FUNC( dragEnd ), para );
         */
         gtk_signal_connect( GTK_OBJECT( widget ), "drag_data_get",
               GTK_SIGNAL_FUNC( dragDataGet ), para );
      }
      /* FIXME: g_free( cmd ) in destroy */
   }
   else
   {
      g_free( para->command );
      g_free( para->name );
      g_free( para );
   }

   g_free( targets );

   return ret;
}

int gnoclDndCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   static char *cmds[] = { "setDestination", "setSource", NULL };
   enum cmdIdx { SetDestinationIdx, SetSourceIdx };

   int idx;

   if( Tcl_GetIndexFromObj( interp, objv[1], cmds, "command", 
         TCL_EXACT, &idx ) != TCL_OK )
      return TCL_ERROR;

   switch( idx )
   {
      case SetDestinationIdx:   
      case SetSourceIdx:        return setDestination( interp, objc, objv, 
                                          idx == SetDestinationIdx );
   }
   return TCL_OK;
}

