/*
 * tkDragdrop.c --
 *
 *	Drag and drop (DND) support functions and commands
 *
 * Copyright (c) 1996 Alfredo K. Kojima
 * 
 * Adaption to tk4.2p2 Copyright (c) 1997 Oliver Graf <ograf@fga.de>
 *
 * Full DND 1.0 support Copyright (c) 1997 Oliver Graf
 */

#include "tkPort.h"
#include "tkInt.h"

#ifndef MAXINT
#define MAXINT (0x80000000)
#endif

#ifdef ENABLE_DND

typedef struct
{
  int	Width,Height;
  char	*ImageData,*MaskData;
  int	HotSpotX,HotSpotY;
  Pixmap	ImagePixmap,MaskPixmap;
  Cursor	CursorID;
} CursorData;

#include "cursor/file.xbm"
#include "cursor/file_mask.xbm"
#include "cursor/files.xbm"
#include "cursor/files_mask.xbm"
#include "cursor/dir.xbm"
#include "cursor/dir_mask.xbm"
#include "cursor/text.xbm"
#include "cursor/text_mask.xbm"
#include "cursor/grey.xbm"
#include "cursor/grey_mask.xbm"
#include "cursor/link.xbm"
#include "cursor/link_mask.xbm"
#include "cursor/app.xbm"
#include "cursor/app_mask.xbm"
#include "cursor/url.xbm"
#include "cursor/url_mask.xbm"
#include "cursor/mime.xbm"
#include "cursor/mime_mask.xbm"
/*
 * Cursors to be used during drags
 */
CursorData DndCursor[]={
  { 0,0,NULL,NULL,0,0,0 },
  { grey_width,	grey_height,	grey_bits,	grey_mask_bits,	grey_x_hot,	grey_y_hot},
  { file_width, file_height,	file_bits,	file_mask_bits,	file_x_hot,	file_y_hot},
  { files_width,files_height,	files_bits,	files_mask_bits,files_x_hot,	files_y_hot},
  { text_width,	text_height,	text_bits,	text_mask_bits,	text_x_hot,	text_y_hot },
  { dir_width,	dir_height,     dir_bits,       dir_mask_bits,	dir_x_hot,	dir_y_hot},
  { link_width,	link_height,	link_bits,	link_mask_bits,	link_x_hot,	link_y_hot},
  { app_width,  app_height,     app_bits,       app_mask_bits,	app_x_hot,	app_y_hot},
  { url_width,	url_height,	url_bits,	url_mask_bits,	url_x_hot,	url_y_hot },
  { mime_width,	mime_height,	mime_bits,	mime_mask_bits,	mime_x_hot,	mime_y_hot },
};

static char *DND_STRING_TYPES[]={"Unknown", "RawData", "File", "Files",
				 "Text", "Dir", "Link", "Exe", "URL",
				 "MIME",NULL};

/*
 * Limit of property size to retrieve
 */
#define MAX_PROP_DATA	1000000L


/*
 *-------------------------------------------------------------------
 * 
 * Tk_DndInitialize --
 * 
 * 	Initialize drag & drop data structures and other stuff in 
 * MainInfo
 * 
 * Result:
 *  	None
 * 
 * Side effects:
 * 	DndProtocol and DndSelection atoms are created 
 * (if they weren't already), and the dndInfo structure in MainInfo 
 * is initialized
 * 
 *------------------------------------------------------------------- 
 */
void
Tk_DndInitialize(interp, tkwin)
    Tcl_Interp	*interp;
    TkWindow *tkwin;
{
    int i;
    XColor   Black,White;
    Colormap colormap;
    Window	root = RootWindowOfScreen(Tk_Screen((Tk_Window)tkwin));
    
    tkwin->mainPtr->dndInfo.DndProtocol=
      Tk_InternAtom((Tk_Window)tkwin,"_DND_PROTOCOL");
    tkwin->mainPtr->dndInfo.DndSelection=
      Tk_InternAtom((Tk_Window)tkwin,"_DND_SELECTION");
    tkwin->mainPtr->dndInfo.BState = 0;
    tkwin->mainPtr->dndInfo.Dragging = 0;
    tkwin->mainPtr->dndInfo.Target = None;
    tkwin->mainPtr->dndInfo.DataSet = 0;
    tkwin->mainPtr->dndInfo.DragPrecision = DRAG_PRECISION;
    colormap = DefaultColormapOfScreen(Tk_Screen((Tk_Window)tkwin));
    Black.pixel=BlackPixelOfScreen(Tk_Screen((Tk_Window)tkwin));
    White.pixel=WhitePixelOfScreen(Tk_Screen((Tk_Window)tkwin));
    XQueryColor(tkwin->display,colormap,&Black);
    XQueryColor(tkwin->display,colormap,&White);

    for(i = 1; i < DndEND ; i++) {
	DndCursor[i].ImagePixmap=
	  XCreateBitmapFromData(tkwin->display, 
		RootWindowOfScreen(Tk_Screen((Tk_Window)tkwin)),
		DndCursor[i].ImageData,
		DndCursor[i].Width,
		DndCursor[i].Height);
	DndCursor[i].MaskPixmap=
	  XCreateBitmapFromData(tkwin->display, 
		RootWindowOfScreen(Tk_Screen((Tk_Window)tkwin)),
		DndCursor[i].MaskData,
		DndCursor[i].Width,
		DndCursor[i].Height);
	DndCursor[i].CursorID=
	  XCreatePixmapCursor(tkwin->display,
		DndCursor[i].ImagePixmap,
		DndCursor[i].MaskPixmap,
		&Black,&White,
		DndCursor[i].HotSpotX,
		DndCursor[i].HotSpotY);	
    }
    DndCursor[0].CursorID=XCreateFontCursor(tkwin->display, XC_question_arrow);
}




/*
 *---------------------------------------------------------------------
 * 
 * Tk_DndSetDataCmd --
 * 
 * 	This procedure is invoked to process the dnd_setdata command.
 * 	It sets up the data to be dragged, it's type and optionally 
 *	it's size.
 * 
 * Results:
 * 	A standard Tcl result.
 * 
 * Side Effects:
 * 	Modifies dndInfo structure and sets the DND selection buffer on
 * 	the X server.
 * 
 *---------------------------------------------------------------------
 */
int
Tk_DndSetDataCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window associated with interpreter. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    Tk_Window tkwin = (Tk_Window) clientData;
    TkMainInfo *mainPtr = ((TkWindow *)tkwin)->mainPtr;
    int bsize, t;
    unsigned long size, tmp;
    char *adata, *data, *d;
      
    if ((argc < 3) || (argc > 4)) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
		" datatype data ?size? \"", (char *) NULL);
	return TCL_ERROR;
    }
    tmp = strlen(argv[2]) + 1; /* don't forget the 0 */
    if (argc == 4) {
	if (Tcl_GetInt(interp, argv[3], (int *) &size) != TCL_OK) {
	    return TCL_ERROR;
	}
    } else {
	size = tmp;
    }
    if (size > tmp) {
	size = tmp;
    }

    t=strtoul(argv[1],&d,0);
    if (*d!=0)
      {
	for (t=0; DND_STRING_TYPES[t]!=NULL; t++)
	  if (strcmp(argv[1], DND_STRING_TYPES[t])==0)
	    break;
	if (DND_STRING_TYPES[t]==NULL)
	  t=-1;
      }
    adata=data=strdup(argv[2]);
    switch (t)
      {
      case -1:
	Tcl_AppendResult(interp, "bad data type \"", argv[1],
			 "\" : must be Unknown, RawData, File, Files, Text, Dir, Link, Exe, URL, MIME or a number",
			 (char *) NULL);
	free(data);
	return TCL_ERROR;
      case DndFile:
      case DndText:
      case DndDir:
      case DndLink:
      case DndExe:
      case DndURL:
      case DndMIME:
	mainPtr->dndInfo.DataType = t;
	break;
      case DndFiles:
    	/* convert '\n' to '\0' for Files */
    	for (d=data; *d; d++)
	    if (*d=='\n') *d=0;
	mainPtr->dndInfo.DataType = t;
	break;
      case DndUnknown:
      case DndRawData:
      default:
	{
	  char *ndata;
	  int i,j;
	  ndata=(char *)malloc(size+1);
	  for (i=j=0; i<size-1; i++) {
	    if (data[i]=='\\') {
	      i++;
	      if (data[i]=='\\')
		ndata[j++]='\\';
	      else if (data[i]>=48 && data[i]<48+32)
		ndata[j++]=data[i]-48;
	      else
		{
		  Tcl_AppendResult(interp, "can't convert data", (char *) NULL);
		  free(data);
		  free(ndata);
		  return TCL_ERROR;
		}
	    } else
	      ndata[j++]=data[i];
	  }
	  ndata[j++]=0;
	  free(data);
	  data=ndata;
	  size=j;
	  mainPtr->dndInfo.DataType = t;
	}
	break;
    }

    bsize = size > MAXINT ? MAXINT : (int)size ;
    XChangeProperty(Tk_Display(tkwin), RootWindow(Tk_Display(tkwin),
		Tk_ScreenNumber(tkwin)),
		mainPtr->dndInfo.DndSelection, XA_STRING,
		8, PropModeReplace, data, bsize);
    size -= (unsigned long)bsize;    
    while (size > 0) {	
	adata += bsize;
	bsize = size > MAXINT ? MAXINT : (int)size ;
	XChangeProperty(Tk_Display(tkwin), RootWindow(Tk_Display(tkwin),
		Tk_ScreenNumber(tkwin)),
		mainPtr->dndInfo.DndSelection, XA_STRING,
		8, PropModeAppend, adata, bsize);
	size -= (unsigned long)bsize;	
    }
    /*
     * signal that data is ready to go
     */
    mainPtr->dndInfo.DataSet = 1;

    free(data);

    return TCL_OK;
}

/*
 *------------------------------------------------------------------------ 
 * 
 *  Tk_DndGetData --
 * 	Gets dropped data from root window. The maximum size of data
 * retrieved if MAX_PROP_DATA
 * 
 *  Returns:
 * 	Pointer to data in a 8 bit string format.  NULL if an error occurs
 *  
 *  Side effects:
 *	It modifies the selection data changing \0 with \n if
 * 	the data type is DndFiles. It also frees up the data used in the
 * 	last call to it.
 *
 *------------------------------------------------------------------------ 
 */
char *
Tk_DndGetData(tkwin, eventPtr)
	Tk_Window  tkwin;
	XEvent     *eventPtr;
{
    TkWindow	*tkw = (TkWindow *)tkwin;
    Atom    atype;
    int     aformat;
    unsigned long rmnsize, size;
    unsigned char *data, *ndata;
    int i,j;

    XGetWindowProperty(eventPtr->xany.display,
		       RootWindow(eventPtr->xany.display,Tk_ScreenNumber(tkwin)),
		       tkw->mainPtr->dndInfo.DndSelection,
		       0, MAX_PROP_DATA, False, AnyPropertyType,
		       &atype,&aformat, &size,&rmnsize, &data);

    if (atype == None) { /* uh oh... */
	panic("XGetWindowProperty says it doesn't know DndSelection...");
    }
    
    switch (eventPtr->xclient.data.l[0])
      {
      case DndText:
      case DndFile:
      case DndExe:
      case DndDir:
      case DndLink:
      case DndURL:
      case DndMIME:
	return data;
	
      case DndFiles:  
	for(i = 0; i < size-1; i++) { /* left last \0 intact */
	  if (data[i] == 0)
	    data[i] = '\n';
	}
	return data;

      case DndRawData:
      case DndUnknown:
      default:
     	ndata=(unsigned char *)malloc(size*2+1);
	for(i=j=0; i < size-1; i++) {
	  if (data[i]<32) /* map anything below space to '\[0-O]' NULL to Oh */
	    {
	      ndata[j++]='\\';
	      ndata[j++]=data[i]+48;
	    } 
	  else if (data[i]=='\\') /* map backslash to double-backslash */
	    {
	      ndata[j++]='\\';
	      ndata[j++]='\\';
	    }
	  else
	    ndata[j++]=data[i];
	}
	ndata[j]=0;
	free(data);
	return ndata;
    }
    return NULL;
}

/*
 *------------------------------------------------------------------------ 
 * 
 *  Tk_DndGetDataSize --
 *	Gets the size of the data dropped on us.
 * 
 *  Returns:
 * 	Amount of data that was dropped 
 *
 *  Side effects:
 *	None
 * 
 *------------------------------------------------------------------------ 
 */
unsigned long
Tk_DndGetDataSize(tkwin, eventPtr)
	Tk_Window  tkwin;
	XEvent     *eventPtr;
{
    Atom    atype;
    int     aformat;
    unsigned long rmnsize, size;
    unsigned char *data;

    XGetWindowProperty(eventPtr->xany.display,
		       RootWindow(eventPtr->xany.display,
			Tk_ScreenNumber((Tk_Window)tkwin)),
		       ((TkWindow *)tkwin)->mainPtr->dndInfo.DndSelection,
		       0, MAX_PROP_DATA, False, AnyPropertyType,
		       &atype,&aformat, &size,&rmnsize, &data);
    return size;
}

/*
 *------------------------------------------------------------------------ 
 * 
 *  Tk_DndGetDataType --
 *	Gets the type of the data dropped on us.
 * 
 *  Returns:
 * 	Type of data that was dropped. -1 if last event was not a drop
 *  
 *  Side effects:
 *	None
 * 
 *------------------------------------------------------------------------ 
 */
int
Tk_DndGetDataType(tkwin, eventPtr)
	Tk_Window  tkwin;	/* not used */
	XEvent     *eventPtr;
{
    if ((eventPtr->type != ClientMessage) || (eventPtr->xclient.message_type!=
	((TkWindow *)tkwin)->mainPtr->dndInfo.DndProtocol)) {
	printf("DndGetDataSize is being called after an invalid event\n");
	return DndUnknown;
    }
    return eventPtr->xclient.data.l[0];
}

/*
 *------------------------------------------------------------------------ 
 * 
 *  Tk_DndGetButtonInfo --
 *	Gets the button that dropped on us.
 * 
 *  Returns:
 * 	Button number. -1 if last event was not a drop
 *  
 *  Side effects:
 *	None
 * 
 *------------------------------------------------------------------------ 
 */
int
Tk_DndGetButtonInfo(tkwin, eventPtr)
	Tk_Window  tkwin;	/* not used */
	XEvent     *eventPtr;
{
    if ((eventPtr->type != ClientMessage) || (eventPtr->xclient.message_type!=
	((TkWindow *)tkwin)->mainPtr->dndInfo.DndProtocol)) {
	printf("DndGetButtonInfo is being called after an invalid event\n");
	return DndUnknown;
    }
    return eventPtr->xclient.data.l[1];
}

/*
 *------------------------------------------------------------------------ 
 * 
 *  Tk_DndGetRootPos --
 *	Gets Drop position.
 * 
 *  Returns:
 * 	-1 if last event was not a drop
 *  
 *  Side effects:
 *	None
 * 
 *------------------------------------------------------------------------ 
 */
int
Tk_DndGetRootPos(tkwin, eventPtr, x, y)
	Tk_Window  tkwin;	/* not used */
	XEvent     *eventPtr;
	int *x, *y;
{
    if ((eventPtr->type != ClientMessage) || (eventPtr->xclient.message_type!=
	((TkWindow *)tkwin)->mainPtr->dndInfo.DndProtocol)) {
	printf("DndGetRootPos is being called after an invalid event\n");
	return DndUnknown;
    }
    *x=eventPtr->xclient.data.l[3]&0xffff;
    *y=(eventPtr->xclient.data.l[3]>>16)&0xffff;
    return 0;
}


/*
 *----------------------------------------------------------------------
 * 
 * Tk_DndHandleDragCmd --
 * 
 *	This procedure is invoked to process the dnd_handledrag command.
 * 	It sets the cursor to be used during the drag, grabs the pointer
 * 	until the user releases the mouse button and signals the
 * 	target.
 * 
 * 	It should be only executed inside of a drag event handler.
 * 
 * Results:
 *	A standard Tcl result.
 * 
 * Side Effects:
 * 	Grabs the pointer and sends a ClientMessage event to the target.
 *
 *---------------------------------------------------------------------
 */
int
Tk_DndHandleDragCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window associated with interpreter. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    Tk_Window tkwin = (Tk_Window) clientData;
    TkMainInfo *mainPtr = ((TkWindow *)tkwin)->mainPtr;
    XEvent	Event;
    Window	root	= RootWindowOfScreen(Tk_Screen(tkwin));
    Window  	DispatchWindow;
    Cursor 	cursor;
    Tk_Cursor  	tkcursor = None;
    int		DropX, DropY;

    if (mainPtr->dndInfo.Dragging != 2) {
	Tcl_AppendResult(interp, "must be called during a drag", 
			 (char *) NULL);
	return TCL_ERROR;
    }

    if (argc > 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
		"?cursor?\"", (char *) NULL);
	return TCL_ERROR;
    }    
   
    if (argc == 2) {
	tkcursor = Tk_GetCursor(interp, tkwin, argv[1]);
	if (tkcursor == None) {
	    return TCL_ERROR;
	}
	cursor = (Cursor)tkcursor;
    } else {
      if (mainPtr->dndInfo.DataType>0
	  && mainPtr->dndInfo.DataType<DndEND)
	cursor = DndCursor[mainPtr->dndInfo.DataType].CursorID;
      else
	cursor = DndCursor[DndRawData].CursorID;
    }    
    XUngrabPointer(Tk_Display(tkwin), CurrentTime);
    XGrabPointer(Tk_Display(tkwin), root, False,
		 ButtonMotionMask|ButtonPressMask|ButtonReleaseMask,
		 GrabModeSync,GrabModeAsync,root,
		 cursor, CurrentTime);
    mainPtr->dndInfo.Dragging = 3;
    mainPtr->dndInfo.RootFlag = 0;
    while (mainPtr->dndInfo.Dragging == 3) {
	XAllowEvents(Tk_Display(tkwin),SyncPointer,CurrentTime);
	XNextEvent(Tk_Display(tkwin),&Event);
	if (Event.type == ButtonRelease) {
	    if (Event.xbutton.subwindow) 
	      mainPtr->dndInfo.RootFlag = 0;
	    else
	      mainPtr->dndInfo.RootFlag = 1;
	    mainPtr->dndInfo.Dragging = 0;
	} else {
	    Tk_HandleEvent(&Event);
	}
    }
    mainPtr->dndInfo.DataSet = 0;
    XUngrabPointer(Tk_Display(tkwin), CurrentTime);
    
    if (tkcursor != None) {
	Tk_FreeCursor(Tk_Display(tkwin), tkcursor);
    }
    
    if (!mainPtr->dndInfo.RootFlag) {
	mainPtr->dndInfo.Target=
	  XmuClientWindow(Tk_Display(tkwin),Event.xbutton.subwindow);
	if (mainPtr->dndInfo.Target == Event.xbutton.subwindow) 
	  /* dropped on an icon */
	  DispatchWindow = mainPtr->dndInfo.Target;
	else 
	  /* droppen on a window */
	  DispatchWindow = PointerWindow;
    } else {
	/* Dropped on root */
	DispatchWindow = mainPtr->dndInfo.Target = mainPtr->winPtr->window;
    }
    DropX=Event.xbutton.x_root;
    DropY=Event.xbutton.y_root;
    Event.xclient.type		= ClientMessage;
    Event.xclient.display	= Tk_Display(tkwin);
    Event.xclient.message_type	= mainPtr->dndInfo.DndProtocol;
    Event.xclient.format	= 32;
    Event.xclient.window	= mainPtr->dndInfo.Target;
    Event.xclient.data.l[0]	= mainPtr->dndInfo.DataType;
    Event.xclient.data.l[1]	= mainPtr->dndInfo.BState;
    Event.xclient.data.l[2]	= Tk_WindowId(tkwin);
    Event.xclient.data.l[3]	= DropX + 65536L*(long)DropY;
    Event.xclient.data.l[4]	= 1;
    
    XSendEvent(Tk_Display(tkwin),DispatchWindow,True,NoEventMask,&Event);

    mainPtr->dndInfo.DataType = 0;
    
    return TCL_OK;
}


#endif /* ENABLE_DND */
