/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
/* 
   DragDrop.cpp -- Drag and Drop classes to hide Motif's API, and work with Desktop files.
   Created: Alastair Gourlay(SGI) c/o Dora Hsu<dora@netscape.com>, 26 Nov 1996
 */



// Classes in this file:
//      XFE_DragBase
//        XFE_DragDesktop
//          XFE_DragNetscape
//      XFE_DropBase
//        XFE_DropDesktop
//          XFE_DropNetscape
//

#include <stdlib.h>
#include <net.h>
#include <netdb.h>
#include <icondata.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <xp_mem.h>
#include <xp_str.h>
#include <xpassert.h>
#include <xfe.h>
#include "DragDrop.h"
#include <Xm/DisplayP.h>

#if defined(SOLARIS)||defined(AIX)||defined(UNIXWARE)
extern "C" int gethostname(char *, int);
#endif

#if defined(SCO_SV)
#include <sys/socket.h>  // Need MAXHOSTNAMELEN
#endif

#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif

#if defined(DEBUG_sgidev) 
#define XDEBUG(x) x
#else
#define XDEBUG(x)
#endif

#ifdef MOZ_MAIL_NEWS
// from libmsg/msgglue.c
extern "C" XP_Bool MSG_RequiresMailMsgWindow(const char*);
extern "C" XP_Bool MSG_RequiresNewsMsgWindow(const char*);
extern "C" XP_Bool MSG_RequiresBrowserWindow(const char*);
#endif

//
// this list defines platforms for which Motif drag and drop is usable.
//

// test - enable Drag and Drop for all platforms - need to find out now
// if there are unavoidable Motif or X11 bugs on any platform.

//#if defined(IRIX) || defined(SOLARIS) || defined(HPUX) || defined(UNIXWARE)
#define DRAG_ENABLED
//#endif
//#if defined(IRIX) || defined(SOLARIS) || defined(HPUX) || defined(UNIXWARE)
#define DROP_ENABLED
//#endif

// default FTR type
static const char DEFAULT_FTR_TYPE[]="Unknown";

// global drag information
Widget XFE_DragBase::_activeDragShell=NULL;

// Atoms, initialized by InitializeDisplayInfo(), called from drag and drop
// base class constructors

Atom XFE_DragBase::_XA_TARGETS=None;
Atom XFE_DragBase::_XA_DELETE=None;
Atom XFE_DragBase::_XA_MULTIPLE=None;
Atom XFE_DragBase::_XA_TIMESTAMP=None;
Atom XFE_DragBase::_XA_NULL=None;
Atom XFE_DragBase::_XA_SGI_ICON=None;
Atom XFE_DragBase::_XA_SGI_ICON_TYPE=None;
Atom XFE_DragBase::_XA_SGI_FILE=None;
Atom XFE_DragBase::_XA_FILE_NAME=None;
Atom XFE_DragBase::_XA_NETSCAPE_URL=None;

Atom XFE_DropBase::_XA_TARGETS=None;
Atom XFE_DropBase::_XA_DELETE=None;
Atom XFE_DropBase::_XA_NULL=None;
Atom XFE_DropBase::_XA_SGI_ICON=None;
Atom XFE_DropBase::_XA_SGI_FILE=None;
Atom XFE_DropBase::_XA_SGI_URL=None;
Atom XFE_DropBase::_XA_FILE_NAME=None;
Atom XFE_DropBase::_XA_NETSCAPE_URL=None;

static char *localHostName=NULL;

static void InitializeDisplayInfo(Widget widget)
{
    static int idi_firstTime=TRUE;
    if (!idi_firstTime)
        return;
    idi_firstTime=FALSE;

    // atoms used for drag and drop
    XFE_DragBase::_XA_TARGETS=XmInternAtom(XtDisplay(widget),"TARGETS",FALSE);
    XFE_DragBase::_XA_DELETE=XmInternAtom(XtDisplay(widget),"DELETE",FALSE);
    XFE_DragBase::_XA_MULTIPLE=XmInternAtom(XtDisplay(widget),"MULTIPLE",FALSE);
    XFE_DragBase::_XA_TIMESTAMP=XmInternAtom(XtDisplay(widget),"TIMESTAMP",FALSE);
    XFE_DragBase::_XA_NULL=XmInternAtom(XtDisplay(widget),"NULL",FALSE);
    XFE_DragBase::_XA_SGI_ICON=XmInternAtom(XtDisplay(widget),"_SGI_ICON",FALSE);
    XFE_DragBase::_XA_SGI_ICON_TYPE=XmInternAtom(XtDisplay(widget),"_SGI_ICON_TYPE",FALSE);
    XFE_DragBase::_XA_SGI_FILE=XmInternAtom(XtDisplay(widget),"SGI_FILE",FALSE);
    XFE_DragBase::_XA_FILE_NAME=XmInternAtom(XtDisplay(widget),"FILE_NAME",FALSE);
    XFE_DragBase::_XA_NETSCAPE_URL=XmInternAtom(XtDisplay(widget),"_NETSCAPE_URL",FALSE);

    XFE_DropBase::_XA_TARGETS=XmInternAtom(XtDisplay(widget),"TARGETS",FALSE);
    XFE_DropBase::_XA_DELETE=XmInternAtom(XtDisplay(widget),"DELETE",FALSE);
    XFE_DropBase::_XA_NULL=XmInternAtom(XtDisplay(widget),"NULL",FALSE);
    XFE_DropBase::_XA_SGI_ICON=XmInternAtom(XtDisplay(widget),"_SGI_ICON",FALSE);
    XFE_DropBase::_XA_SGI_FILE=XmInternAtom(XtDisplay(widget),"SGI_FILE",FALSE);
    XFE_DropBase::_XA_SGI_URL=XmInternAtom(XtDisplay(widget),"_SGI_URL",FALSE);
    XFE_DropBase::_XA_FILE_NAME=XmInternAtom(XtDisplay(widget),"FILE_NAME",FALSE);
    XFE_DropBase::_XA_NETSCAPE_URL=XmInternAtom(XtDisplay(widget),"_NETSCAPE_URL",FALSE);

    // local hostname, used to process _SGI_ICON drag
    char h[MAXHOSTNAMELEN+1];
    if (gethostname(h, MAXHOSTNAMELEN)==0) {
        h[MAXHOSTNAMELEN]='\0';
        localHostName=strdup(h);
    }
    else {
        localHostName="localhost";
    }
}


//
// XFE_DragBase
//

// globally useful utilities

char *XFE_DragBase::guessUrlMimeType(const char *data)
{    
    // try to determine type of attachment

    // regular file
    if (XP_STRNCASECMP(data,"file:",5)==0 || NET_URL_Type(data)==0) {
        const char *fileData=data;
        if (XP_STRNCASECMP(data,"file:",5)==0) // skip file: prefix
            fileData=data+5;
        if (fe_isDir((char*)fileData))
            return "_directory"; // interal type, since there's no mime type
        NET_cinfo *pMimeInfo = NET_cinfo_find_type((char*)data);
        if ((pMimeInfo) && (pMimeInfo->type))
            return pMimeInfo->type;
        else
            return APPLICATION_OCTET_STREAM;
    }

    // addressbook entry
    else if (XP_STRNCASECMP(data,"addbook:",8)==0)
        return "text/x-vcard";

#ifdef MOZ_MAIL_NEWS
    // mail or news message
    else if (MSG_RequiresMailMsgWindow(data) || MSG_RequiresNewsMsgWindow(data))
        return "message/rfc822";

    // document URL - use internal id, since there's no URL mime type
    else if (MSG_RequiresBrowserWindow(data))
        return "_url";
#endif

    // fallback to generic type
    else
        return UNKNOWN_CONTENT_TYPE;
}


// callback wrappers
void XFE_DragBase::ButtonPressCb(Widget w,XtPointer cd,XEvent *event,Boolean*)
{
    XFE_DragBase *d=(XFE_DragBase*)cd;
    if (d &&
        event->xany.type==ButtonPress &&
        event->xbutton.button==1) {
        d->_dragWidget=w;
        d->buttonPressCb((XButtonPressedEvent*)event);
    }
}

void XFE_DragBase::ButtonMotionCb(Widget w,XtPointer cd,XEvent *event,Boolean *eatEvent)
{
    XFE_DragBase *d=(XFE_DragBase*)cd;
    if (d &&
        w==d->_dragWidget &&
        event->xany.type==MotionNotify) {
        *eatEvent=d->buttonMotionCb((XMotionEvent*)event);
    }    
}

Boolean XFE_DragBase::DragConvertCb(Widget w,Atom*,Atom *target,Atom *type,
                                    XtPointer *value,unsigned long *length,int *format)
{
    if (!w || !XmIsDragContext(w))
        return FALSE;
        
    XtPointer cd;
    XtVaGetValues(w,XmNclientData,&cd,NULL);
    XFE_DragBase *d=(XFE_DragBase*)cd;

    if (d) {
        return d->dragConvertCb(target,type,value,length,format);
    }
    return FALSE;
}

void XFE_DragBase::OperationChangedCb(Widget w,XtPointer cd,XtPointer)
{
    if (!w || !XmIsDragContext(w))
        return;
        
    XFE_DragBase *d=(XFE_DragBase*)cd;

    // nyi - what callback struct info should be passed to method?
    if (d) {
        d->operationChangedCb();
    }
}

void XFE_DragBase::DragMotionCb(Widget w,XtPointer cd,XtPointer cb)
{
    if (!w || !XmIsDragContext(w))
        return;
        
    XFE_DragBase *d=(XFE_DragBase*)cd;
    XmDragMotionCallbackStruct *dmcb=(XmDragMotionCallbackStruct*)cb;

    // nyi - what callback struct info should be passed to method?
    if (d) {
        // record (x,y) of pointer during drag
        if (dmcb) {
            d->_dragEventX=dmcb->x;
            d->_dragEventY=dmcb->y;
        }        
        d->dragMotionCb();
    }
}

void XFE_DragBase::DropStartCb(Widget w,XtPointer cd,XtPointer)
{
    if (!w || !XmIsDragContext(w))
        return;
        
    XFE_DragBase *d=(XFE_DragBase*)cd;

    // nyi - what callback struct info should be passed to method?
    if (d) {
        d->dropStartCb();
    }
}

void XFE_DragBase::DropSiteEnterCb(Widget w,XtPointer cd,XtPointer cb)
{
    if (!w || !XmIsDragContext(w))
        return;
        
    XFE_DragBase *d=(XFE_DragBase*)cd;

    XmDropSiteEnterCallbackStruct *dcb=(XmDropSiteEnterCallbackStruct*)cb;
    
    // nyi - what callback struct info should be passed to method?
    if (d) {
        d->dropSiteEnterCb(dcb->operations);
    }
    XDEBUG(printf("  operation=%x, operations=%x,status=%d\n",
                  dcb->operation,dcb->operations,dcb->dropSiteStatus));
}

void XFE_DragBase::DropSiteLeaveCb(Widget w,XtPointer cd,XtPointer)
{
    if (!w || !XmIsDragContext(w))
        return;
        
    XFE_DragBase *d=(XFE_DragBase*)cd;

    // nyi - what callback struct info should be passed to method?
    if (d) {
        d->dropSiteLeaveCb();
    }
}

void XFE_DragBase::DropFinishCb(Widget w,XtPointer cd,XtPointer)
{
    if (!w || !XmIsDragContext(w))
        return;
        
    XFE_DragBase *d=(XFE_DragBase*)cd;

    // nyi - what callback struct info should be passed to method?
    if (d) {
        d->dropFinishCb();
    }
}

void XFE_DragBase::DragDropFinishCb(Widget w,XtPointer cd,XtPointer)
{
    if (!w || !XmIsDragContext(w))
        return;
        
    XFE_DragBase *d=(XFE_DragBase*)cd;

    // nyi - what callback struct info should be passed to method?
    if (d) {
        d->dragDropFinishCb();
    }
}

void XFE_DragBase::DestroyCb(Widget w,XtPointer,XtPointer)
{
    if (!w || !XmIsDragContext(w))
        return;
        
    //XFE_DragBase *d=(XFE_DragBase*)cd;

    XDEBUG(printf("XFE_DragBase::DestroyCb()\n"));
}

// constructor

// The drag classes will monitor drags from the widget passed
// to the constructor. Simple drag classes can just pass
// one widget to the constructor. Multi-item objects can use
// one XFE_Drag class with additional widgets, specified by
// addDragWidget(). During the drag, the field _dragWidget
// will point to the widget currently being dragged.

XFE_DragBase::XFE_DragBase(Widget widget)
{
    _widget=widget;
    _dragWidget=NULL;
    _dragThreshold=4;
    _dragStarted=FALSE;
    _buttonPressed=FALSE;
    _dragStartX=0;
    _dragStartY=0;
    _dragEventX=0;
    _dragEventY=0;
    _dragContext=NULL;
    _dragIcon=NULL;    
    _targets=NULL;
    _numTargets=0;
    _dragFilesAsLinks=FALSE;

    // default icon is blank document
    _dragIconData=&GUnknown;
    _dragIconPixmap=None;
    _dragIconPixmapMask=None;
    _dragIconWidth=0;
    _dragIconHeight=0;

    InitializeDisplayInfo(widget);

    addDragWidget(widget);

#ifdef IRIX
    // hack for SGI Motif - force it to sgidladd/dlopen conversion library
    // now, not on the first drag - it can be slow, and blows the usability
    // out the water - an early button release leaves a permanent cursor
    // window turd. Note: performance hit is present only in 6.2 and
    // 6.3. 6.3-R10K and 6.4 were improved greatly.
    static int firstTime=TRUE;
    if (firstTime) {
        XDEBUG(printf("Hacking XmDragContext initialization {\n"));
        firstTime=FALSE;
        Atom tl[1]={ XA_STRING };
        Arg args[2];
        int n=0;
        XtSetArg(args[n],XmNexportTargets,&tl);n++;
        XtSetArg(args[n],XmNnumExportTargets,1);n++;
        XtDestroyWidget(XtCreateWidget("dragContext",xmDragContextClass,
                                       XmGetXmDisplay(XtDisplay(widget)),
                                       args,n));
        XDEBUG(printf("}\n"));
    }
#endif
}


// destructor

XFE_DragBase::~XFE_DragBase()
{
    XDEBUG(printf("XFE_DragBase::~XFE_DragBase()\n"));
    
    // destroy drag icon widget
    // usually destroyed in dragDropFinishCb, but catch case of mid-drag Destroy.
    if (_dragIcon) {
        XtDestroyWidget(_dragIcon);
        _dragIcon=NULL;
    }

    // clean up drag icon data
    setDragIcon(NULL);
}


//
// drag methods
//

void XFE_DragBase::addDragWidget(Widget widget)
{    
    if (widget) {
        // catch button 1 press and motion
        XtInsertEventHandler(widget,ButtonPressMask,FALSE,
                             ButtonPressCb,(XtPointer)this,XtListHead);
        XtInsertEventHandler(widget,Button1MotionMask,FALSE,
                             ButtonMotionCb,(XtPointer)this,XtListHead);

        // clean up when widget is destroyed
        XtAddCallback(widget,XmNdestroyCallback,dragWidgetDestroyCb,(XtPointer)this);
    }
}

void XFE_DragBase::dragWidgetDestroyCb(Widget widget,XtPointer cd,XtPointer)
{
    if (widget && cd) {
        XDEBUG(printf("XFE_DragBase::dragWidgetDestroyCb(%s)\n",XtName(widget)));
        XtRemoveEventHandler(widget,ButtonPressMask,FALSE,
                             ButtonPressCb,cd);
        XtRemoveEventHandler(widget,Button1MotionMask,FALSE,
                             ButtonMotionCb,cd);
        // _dragIcon widget is a child of the _dragWidget.
        // It will get destroyed by Xt, so clean up our pointer.
        XFE_DragBase *db=(XFE_DragBase*)cd;
        db->_dragIcon=NULL;
    }
}

void XFE_DragBase::dragInitialize()
{
    if (_dragContext!=NULL) {
        XDEBUG(printf("DRAG IN PROGRESS - new drag not started\n"));
        return;
    }

    if (!_dragWidget)
        return;
    
    _dragFilesAsLinks=FALSE;
    
    // let derived class decide how to handle this drag
    if (dragStart(_dragStartX,_dragStartY)==FALSE) {
        dragDropFinishCb();
        return;
    }
    
    targets();
    operations();

    if (_numTargets==0 || _operations==0) {
        dragDropFinishCb();
        return;
    }

    // enable desktop LINK behavior, if application class asks
    if (_dragFilesAsLinks) {
        _operations|=(unsigned int)XmDROP_LINK;
    }
    
    _dragIcon=NULL;
    // create drag icons if necessary
    // (i.e. first time, and after setDragIcon() )
#ifdef DRAG_ENABLED
    if (_dragIconData) {
        if (_dragIconPixmap==None)
            _dragIconPixmap=XCreateBitmapFromData(XtDisplay(_dragWidget),
                                                  XtWindow(_dragWidget),
                                                  (char*)_dragIconData->mono_bits,
                                                  _dragIconData->width,
                                                  _dragIconData->height);
        if (_dragIconPixmapMask==None)
            _dragIconPixmapMask=XCreateBitmapFromData(XtDisplay(_dragWidget),
                                                      XtWindow(_dragWidget),
                                                      (char*)_dragIconData->mask_bits,
                                                      _dragIconData->width,
                                                      _dragIconData->height);
        Arg args[20];
        int n=0;
        XtSetArg(args[n],XmNwidth,_dragIconData->width);n++;
        XtSetArg(args[n],XmNheight,_dragIconData->height);n++;
        XtSetArg(args[n],XmNpixmap,_dragIconPixmap);n++;
        if (_dragIconPixmapMask) {
            XtSetArg(args[n],XmNmask,_dragIconPixmapMask);n++;
        }
        _dragIcon=XmCreateDragIcon(_dragWidget,"dragIcon",args,n);
#endif
    }

    // callback records
    static XtCallbackRec operationChangedCbRec[]= {
        {OperationChangedCb,NULL},{NULL,NULL}
    };
    static XtCallbackRec dragMotionCbRec[]= {
        {DragMotionCb,NULL},{NULL,NULL}
    };
    static XtCallbackRec dropStartCbRec[]= {
        {DropStartCb,NULL},{NULL,NULL}
    };
    static XtCallbackRec dropSiteEnterCbRec[]= {
        {DropSiteEnterCb,NULL},{NULL,NULL}
    };
    static XtCallbackRec dropSiteLeaveCbRec[]= {
        {DropSiteLeaveCb,NULL},{NULL,NULL}
    };
    static XtCallbackRec dropFinishCbRec[]= {
        {DropFinishCb,NULL},{NULL,NULL}
    };
    static XtCallbackRec dragDropFinishCbRec[]= {
        {DragDropFinishCb,NULL},{NULL,NULL}
    };
    static XtCallbackRec destroyCbRec[]= {
        {DestroyCb,NULL},{NULL,NULL}
    };
    operationChangedCbRec[0].closure=this;
    dragMotionCbRec[0].closure=this;
    dropStartCbRec[0].closure=this;
    dropSiteEnterCbRec[0].closure=this;
    dropSiteLeaveCbRec[0].closure=this;
    dropFinishCbRec[0].closure=this;
    dragDropFinishCbRec[0].closure=this;
    destroyCbRec[0].closure=this;
    
    Arg args[40];
    int n=0;
    XtSetArg(args[n],XmNexportTargets,_targets);n++;
    XtSetArg(args[n],XmNnumExportTargets,_numTargets);n++;
    XtSetArg(args[n],XmNclientData,(XtPointer)this);n++;
    XtSetArg(args[n],XmNconvertProc,DragConvertCb);n++;
    XtSetArg(args[n],XmNdragOperations,_operations);n++;
    XtSetArg(args[n],XmNblendModel,XmBLEND_JUST_SOURCE);n++;
    XtSetArg(args[n],XmNoperationChangedCallback,operationChangedCbRec);n++;
    XtSetArg(args[n],XmNdragMotionCallback,dragMotionCbRec);n++;
    XtSetArg(args[n],XmNdropStartCallback,dropStartCbRec);n++;
    XtSetArg(args[n],XmNdropSiteEnterCallback,dropSiteEnterCbRec);n++;
    XtSetArg(args[n],XmNdropSiteLeaveCallback,dropSiteLeaveCbRec);n++;
    XtSetArg(args[n],XmNdropFinishCallback,dropFinishCbRec);n++;
    XtSetArg(args[n],XmNdragDropFinishCallback,dragDropFinishCbRec);n++;
    XtSetArg(args[n],XmNdestroyCallback,destroyCbRec);n++;
    if (_dragIcon) {
#ifdef IRIX
#ifndef IRIX5_3
        // We want the cursor colors to be black and white, but
        // only IRIX Motif does the right thing with the color
        // settings. Other platforms spew many X protocol errors
        // from inside Xm/DragOverS.c in Motif.
        //
        // ifdef desired code for IRIX and let other platforms
        // use default colors until we can figure out a way
        // to stop the X protocol errors.
        Pixel fg,bg;

        // get a context
        MWContext *context=fe_WidgetToMWContext(_dragWidget);
        if (!context)
            context=XP_GetNonGridContext(fe_all_MWContexts->context);

        // extract color info
        if (context) {
            fg=fe_GetPixel(context,0,0,0);
            bg=fe_GetPixel(context,0xff,0xff,0xff);
        }
        else {
            XtVaGetValues(_dragWidget,XmNforeground,&fg,XmNbackground,&bg,NULL);
        }
        XtSetArg(args[n],XmNcursorBackground,bg);n++;
        XtSetArg(args[n],XmNcursorForeground,fg);n++;
#endif
#endif
        XtSetArg(args[n],XmNsourceCursorIcon,_dragIcon);n++;        
    }

#if 0
    // This seems fixed for now..  but I'm not deleting this code just yet,
    // since I don't know the who or the how of the fix.
    //
    // What a hack - Something in the browser menu code is causing
    // the menu system to think it's active each time a new browser
    // is created. This prevents any drags from working until
    // the first menu pops up, then down again, resetting the
    // 'userGrabbed' flag in XmDisplay.
    // We will reset that flag here to make dragging work. This
    // is safe, since we know that we only call XmDragStart() in
    // response to a button press - which will have already deactivated
    //  any menu code. Cool. Now as a warm-down send a manned mission
    // to Mars. Then find out why this happens in the first place!!!!
    Widget xmDisplay=XmGetXmDisplay(XtDisplay(_widget));
    XDEBUG(printf("XmDisplay.userGrabbed=%d\n",((XmDisplayRec*)xmDisplay)->display.userGrabbed));
    ((XmDisplayRec*)xmDisplay)->display.userGrabbed=0;
#endif
    
#ifndef DISABLE_DRAG
    // check that Btn1 is still down
    Window rootWin,childWin;
    int rootX,rootY,childX,childY;
    unsigned int buttonMask=0;

    XSync(XtDisplay(_widget),FALSE);
    XQueryPointer(XtDisplay(_widget),XtWindow(_widget),
                  &rootWin,&childWin,
                  &rootX,&rootY,&childX,&childY,
                  &buttonMask);
    if (!(buttonMask & Button1Mask)) {
        XDEBUG(printf("###Button1 not down - aborting drag!###\n"));
        dragDropFinishCb();
        return;
    }    
    
    XDEBUG(printf("XmDragStart() {\n"));
    _dragContext=XmDragStart(_dragWidget,(XEvent*)&_dragButtonEvent,args,n);
    XDEBUG(printf("}  XmDragStart()\n"));
    if (_dragContext) {
        _dragStarted=TRUE;
        // record global active drag shell
        Widget shell=_widget;
        while (!XtIsShell(shell)) shell=XtParent(shell);
        _activeDragShell=shell;
    }
    else {
        XDEBUG(printf("XmDragStart() failed!\n"));
        dragDropFinishCb();
        return;
    }    
#endif    
}

int XFE_DragBase::isFileURL(const char *url)
{
    return (url && strlen(url)>0 &&
            (XP_STRNCASECMP(url,"file:",5)==0 || XP_STRNCMP(url,"/",1)==0))
        ? TRUE : FALSE;
}

void XFE_DragBase::setDragIcon(struct fe_icon_data *iconData)
{
    // if this is a new icon, clean up old icon data
    if (_dragIconData!=iconData) {
        if (_dragIconPixmap!=None) {
            XFreePixmap(XtDisplay(_widget),_dragIconPixmap);
            _dragIconPixmap=None;
        }
        if (_dragIconPixmapMask!=None) {
            XFreePixmap(XtDisplay(_widget),_dragIconPixmapMask);
            _dragIconPixmapMask=None;
        }
    }

    // set up pointer, and let dragInitialize() create pixmaps
    _dragIconData=iconData;
}

void XFE_DragBase::setDragIconForType(const char *dataType)
{
    if (!dataType) {
        setDragIcon(&GUnknown);
        return;
    }
    
    if (!dataType || XP_STRCASECMP(dataType,UNKNOWN_CONTENT_TYPE)==0)
        setDragIcon(&GUnknown);
    else if (XP_STRCASECMP(dataType,"_url")==0)    // internal type name for urls
        setDragIcon(&LocationProxy);
    else if (XP_STRCASECMP(dataType,"_directory")==0)    // internal type name for directories
        setDragIcon(&GFolder);
    else if (XP_STRNCASECMP(dataType,"audio",5)==0)
        setDragIcon(&GAudio);
    else if (XP_STRNCASECMP(dataType,"application",11)==0)
        setDragIcon(&GBinary);
    else if (XP_STRNCASECMP(dataType,"image",5)==0)
        setDragIcon(&GImage);
#ifdef MOZ_MAIL_NEWS
    else if (XP_STRNCASECMP(dataType,"message",7)==0)
        setDragIcon(&MNTB_Forward);
    else if (XP_STRCASECMP(dataType,"text/x-vcard")==0)
        setDragIcon(&MNAB_NewPerson);
#endif
    else if (XP_STRNCASECMP(dataType,"text",4)==0)
        setDragIcon(&GText);
    else if (XP_STRNCASECMP(dataType,"video",5)==0)
        setDragIcon(&GMovie);
    else
        setDragIcon(&GUnknown);
}


//
// default drag virtual methods - derived class can override
//

// drag string data by default
void XFE_DragBase::targets()
{
    _numTargets=1;
    _targets=new Atom[_numTargets];

    _targets[0]=XA_STRING;
}

// allow move or copy by default
void XFE_DragBase::operations()
{
    _operations=(unsigned int)(XmDROP_MOVE | XmDROP_COPY);
}


// provide data for requested target from targets() list
char *XFE_DragBase::getTargetData(Atom target)
{
    // WARNING - data *must* be allocated with Xt malloc API, or Xt
    // will spring a leak!
    
    if (target==XA_STRING) {
        return (char*)XtNewString("Netscape 4.0 Default Drag Data");
    }

    return 0;
}


void XFE_DragBase::deleteTarget()
{
    XDEBUG(printf("XFE_DragBase::deleteTarget()\n"));
}

//
// button event handler methods
//

extern "C" void fe_HTMLDragSetLayer(CL_Layer *layer);

void XFE_DragBase::buttonPressCb(XButtonPressedEvent *event) 
{
    XDEBUG(printf("XFE_DragBase::buttonPressCb(%d,%d)\n",event->x,event->y));

    // Hack: Clear global HTMLView drag layer before button event is processed by XFE_HTMLView
    fe_HTMLDragSetLayer(NULL);

    // nyi - for now, don't lock out drags when Motif gets confused
    _dragStarted=FALSE;
    
    // one at a time please..
    if (_dragStarted)
        return;

    _buttonPressed=TRUE;
    _dragStartX=event->x;
    _dragStartY=event->y;
    _dragEventX=_dragStartX;
    _dragEventY=_dragStartY;
    
    // gross - copy button event for later use by XmDragStart
    // Note: Motif should allow Motion event in XmDragStart, docs
    // say Yes, but the Xm code disagrees - only ButtonPress is accepted.
    _dragButtonEvent=*event;

    // if there's no threshold, start drag immediately
    if (_dragThreshold==0) {
        dragInitialize();
        _buttonPressed=FALSE; // reset the trigger
    }

}

int XFE_DragBase::buttonMotionCb(XMotionEvent *event)
{
    // steal event if we're already dragging
    if (_dragStarted)
        return FALSE;

    // pass through event if we're not dragging 
    if (!_buttonPressed)
        return TRUE;
    
    XDEBUG(printf("XFE_DragBase::buttonMotionCb(%d,%d)\n",event->x,event->y));

    if (abs(_dragStartX-event->x)>=_dragThreshold ||
        abs(_dragStartY-event->y)>=_dragThreshold) {
        dragInitialize();
        _buttonPressed=FALSE; // reset the trigger
    }

    // steal event - user might be starting a drag
    return FALSE;
}

//
// drag callbacks
//

Boolean XFE_DragBase::dragConvertCb(Atom *target,Atom *type,XtPointer *value,
                                    unsigned long *length,int *format)
{
    XDEBUG(printf("XFE_DragBase::dragConvertCb(%s)\n",XmGetAtomName(XtDisplay(_widget),*target)));
    
    // TARGETS - our targets, plus ICCCM standard targets
    if (*target==_XA_TARGETS) {
        // create targets list - freed by Xt selection API
        Atom *targetData=(Atom*)XtMalloc((unsigned)((_numTargets+4)*sizeof(Atom)));
        int i;
        for(i=0;i<_numTargets;i++)
            targetData[i]=_targets[i];
        targetData[i++]=_XA_DELETE;
        targetData[i++]=_XA_TARGETS;
        targetData[i++]=_XA_MULTIPLE; // provided by Xt
        targetData[i++]=_XA_TIMESTAMP; // provided by Xt

        // return ICCCM targets list
        *value=(XtPointer)targetData;
        *type=XA_ATOM;
        *length=_numTargets+4;
        *format=32;
        return TRUE;
    }
    
    // DELETE
    if (*target==_XA_DELETE) {
        // handle delete request for dragged object
        deleteTarget();
        
        // return ICCCM acknowledgement of delete
        *value=NULL;
        *type=_XA_NULL;
        *length=0;
        *format=8;
        return TRUE;
    }

    // is it a request for the data as a desktop file?
    if (isFileTarget(*target)) {
        // write drag data in appropriate desktop file format, return filenames
        // DragBase does not handle files - methods are provided by derived class.
        // (but this is easier than adding yet another chained virtual method..)
        getTargetDataAsFileWrapper(*target,value,length);
        if (*value && *length>0) {
            if (*target==_XA_FILE_NAME)
                *type=XA_STRING;
            else
                *type=*target;
            *format=8;
            return TRUE;
        }
        else {
            return FALSE;
        }
    }
    
    // All other targets - ask derived class method for data string
    if ((*value=(XtPointer)getTargetData(*target))!=NULL) {
        *type=*target;
        *length=strlen((char*)*value);
        *format=8;
#if defined(DEBUG_tao)
		printf("\nXFE_DragBase::dragConvertCb:value=%x,target=%d,length=%d\n",
			   *value, *target, *length);
#endif

        return TRUE;
    }

    // Can't provide data for requested target
    return FALSE;
}

void XFE_DragBase::operationChangedCb()
{
    XDEBUG(printf("XFE_DragBase::operationChangedCb()\n"));
}

void XFE_DragBase::dragMotionCb()
{
    //XDEBUG(printf("XFE_DragBase::dragMotionCb()\n"));
}

void XFE_DragBase::dropStartCb()
{
    XDEBUG(printf("XFE_DragBase::dropStartCb()\n"));
}

void XFE_DragBase::dropSiteEnterCb(int operations)
{
    XDEBUG(printf("XFE_DragBase::dropSiteEnterCb()\n"));
    if (_dragFilesAsLinks && (operations & XmDROP_LINK)) {
        XtVaSetValues(_dragContext,XmNdragOperations,XmDROP_LINK,NULL);
    }
}

void XFE_DragBase::dropSiteLeaveCb()
{
    XDEBUG(printf("XFE_DragBase::dropSiteLeaveCb()\n"));
    if (_dragFilesAsLinks)
        XtVaSetValues(_dragContext,XmNdragOperations,_operations|XmDROP_LINK,NULL);
}

void XFE_DragBase::dropFinishCb()
{
    XDEBUG(printf("XFE_DragBase::dropFinishCb()\n"));
}

void XFE_DragBase::dragDropFinishCb()
{
    XDEBUG(printf("XFE_DragBase::dragDropFinishCb()\n"));

    dragComplete();

    // destroy drag icon widget
    if (_dragIcon) {
        XtDestroyWidget(_dragIcon);
        _dragIcon=NULL;
    }
    
    // delete targets list
    
    if (_targets) {
        delete [] _targets;
        _targets=NULL;
        _numTargets=0;
    }

    // reset drag data

    _operations=0;
    _dragWidget=NULL;
    _dragContext=NULL;
    _dragStarted=FALSE;
    _dragStartX=0;
    _dragStartY=0;
    _dragEventX=0;
    _dragEventY=0;
    _activeDragShell=NULL;
}



//
// XFE_DragDesktop
//
// app specifies dragging type via targets()
// if request comes in for file:
//      determine actual type from targets()
//      call getTargetData() for actual type
//      create tmp file in correct format
//      return SGI_ICON/SGI_FILE/FILENAME as needed
//      delete file when drop compete (assume force a file copy)
//      DELETE still means delete data in Netscape, not file

XFE_DragDesktop::XFE_DragDesktop(Widget w) : XFE_DragBase(w)
{
    _fileTarget=None;
}

XFE_DragDesktop::~XFE_DragDesktop()
{
}

void XFE_DragDesktop::setFileTarget(Atom target)
{
    if (!_targets || _numTargets==0)
        return;
    
    _fileTarget=target;
    
    // insert desktop file dnd atoms into list
    // must insert before generic STRING - some desktops (IRIX 6.3+)
    // will treat STRING drop as a paste-to-new-file action.
    Atom *newTargets=new Atom[_numTargets+4];
    int i=0;
    int ni=0;
    int added=FALSE;
    while (i<_numTargets) {
        if (_targets[i]==XA_STRING) {
            newTargets[ni++]=_XA_SGI_ICON_TYPE;
            newTargets[ni++]=_XA_SGI_ICON;
            newTargets[ni++]=_XA_SGI_FILE;
            newTargets[ni++]=_XA_FILE_NAME;
            added=TRUE;
        }
        newTargets[ni++]=_targets[i++];
    }
    if (!added) {
        // no STRING in targets list, so append
        newTargets[ni++]=_XA_SGI_ICON_TYPE;
        newTargets[ni++]=_XA_SGI_ICON;
        newTargets[ni++]=_XA_SGI_FILE;
        newTargets[ni++]=_XA_FILE_NAME;
    }

    delete [] _targets;
    _targets=newTargets;
    _numTargets=ni;
}


int XFE_DragDesktop::isFileTarget(Atom target)
{
    return (target==_XA_SGI_ICON ||
            target==_XA_SGI_ICON_TYPE ||
            target==_XA_SGI_FILE ||
            target==_XA_FILE_NAME)
        ? TRUE : FALSE;
}

void XFE_DragDesktop::getTargetDataAsFileWrapper(Atom target, XtPointer *value, unsigned long *length)
{
    // create files for data and return desktop formatted description of files.

    *value=NULL;
    *length=0;

    XDEBUG(printf("XFE_DragDesktop::getTargetDataAsFileWrapper(%s)\n",XmGetAtomName(XtDisplay(_widget),target)));
    
    if (_fileTarget==None)
        return;

    // return type for desktop feedback during drag
    if (target==_XA_SGI_ICON_TYPE) {
        const char *ftrType=getTargetFTRType(_fileTarget);
        if (ftrType) {
            *value=(XtPointer)XtNewString(ftrType);
            *length=strlen(ftrType);
        }
        else {
            *value=NULL;
            *length=0;
        }
        return;
    }

    // get data as list of files
    // (usually one file per selected item, but it's up to derived class)
    char **fileList=NULL;
    int numFiles=0;
    getTargetDataAsFileList(_fileTarget,&fileList,&numFiles);
    
    if (!fileList || numFiles==0)
        return;

    const char *ftrType=getTargetFTRType(_fileTarget);
    const char SGI_ICON_FORMAT[] =
        "Category:%s Name:%s Type:%s Host:%s ViewX:%d ViewY:%d ViewGallery:%d";
    int i;
    int fileDataSize=0;
    // calculate space needed for formatted file list
    for (i=0;i<numFiles;i++)
        if (fileList[i]) fileDataSize+=strlen(fileList[i])+1;
    if (target==_XA_SGI_ICON)
        fileDataSize+=numFiles*(strlen(SGI_ICON_FORMAT)+
                                strlen(ftrType)+
                                strlen(localHostName)+
                                50); // format string space plus ViewX,ViewY,ViewGallery + final \0
    else
        fileDataSize+=1; // + final \0

    // copy file list to value in appropriate format
    char *fileData=XtMalloc(fileDataSize);
    unsigned long fileDataLength=0;
    char *curPos=fileData;
    for (i=0;i<numFiles;i++) {
        if (target==_XA_SGI_ICON) {
            // nyi - tune values for ViewX,ViewY s.t. drop of icon looks right
            // nyi - do we need to tweak ViewX,ViewY for multiple item drops?  (orientation,spacing)
            sprintf(curPos,SGI_ICON_FORMAT,"File",fileList[i],ftrType,localHostName,
                    -10,-10,0);
        }
        else {
            strcpy(curPos,fileList[i]);
        }
        int segmentLen=strlen(curPos)+1;
        fileDataLength+=segmentLen;
        curPos+=segmentLen;
    }
    *curPos='\0'; // terminate with extra \0
    fileDataLength++;

    // free file list
    for (i=0;i<numFiles;i++)
        if (fileList[i]) XP_FREE(fileList[i]);
    delete fileList;
    
    *value=(XtPointer)fileData;
    *length=fileDataLength;
}


void XFE_DragDesktop::getTargetDataAsFileList(Atom,char ***fileList,int *numFiles)
{
    *fileList=NULL;
    *numFiles=0;
    return;
}

const char *XFE_DragDesktop::getTargetFTRType(Atom)
{
    return DEFAULT_FTR_TYPE;
}

void XFE_DragDesktop::dragDropFinishCb()
{
    XDEBUG(printf("XFE_DragDesktop::dragDropFinishCb()\n"));
    
    XFE_DragBase::dragDropFinishCb();

    // reset drag data
    _fileTarget=None;    
}


//
// XFE_DragNetscape
//      - provide support for all Netsape drag types
// and their conversion to a desktop file format. No actual
// drag targets are specified by this class. Derived class
// must specify targets(), operations() and getTargetData()
//

XFE_DragNetscape::XFE_DragNetscape(Widget w) : XFE_DragDesktop(w)
{
    _desktopFileData=NULL;
}

XFE_DragNetscape::~XFE_DragNetscape()
{
    if (_desktopFileData) {
        _desktopFileData->cleanupDataFiles();
        delete _desktopFileData;
        _desktopFileData=NULL;
    }
}


void XFE_DragNetscape::getTargetDataAsFileList(Atom target,char ***fileList,int *numFiles)
{
    *numFiles=0;
    *fileList=NULL;

    if (_desktopFileData!=NULL) {
        // make sure we only have one set of tmp files per drag
        // (dumb or malicious drop site might ask for FILE_NAME and _SGI_ICON)
        return;
    }
    
    XDEBUG(printf("XFE_DragNetscape::getTargetDataAsFileList(%s)\n",XmGetAtomName(XtDisplay(_widget),target)));
           
    // XA_STRING - interpret data as name of existing file
    if (target==XA_STRING) {
        char *filename=getTargetData(XA_STRING);
        if (filename) {
            *numFiles=1;
            *fileList=new char*[1];
            (*fileList)[0]=filename;
        }
        return;
    }

    // XA_FILE_NAME - interpret data as name of existing file
    if (target==_XA_FILE_NAME) {
        char *filename=getTargetData(_XA_FILE_NAME);
        if (filename) {
            *numFiles=1;
            *fileList=new char*[1];
            (*fileList)[0]=filename;
        }
        return;
    }

    // _XA_NETSCAPE_URL - translate data into NetscapeURL desktop file(s)
    if (target==_XA_NETSCAPE_URL) {
        char *stringData=getTargetData(_XA_NETSCAPE_URL);
        if (!stringData)
            return;

        XFE_URLDesktopType *urlData=new XFE_URLDesktopType(stringData);
        *numFiles=urlData->numItems();
        if (*numFiles==0) {
            delete urlData;
            XtFree(stringData);
            return;
        }
        

        // write files to tmp directory
        urlData->writeDataFiles(_dragFilesAsLinks);
        

        // extract filenames of tmp files for drag response
        *fileList=new char*[*numFiles];
        int i;
        for (i=0;i<*numFiles;i++) {
            if (urlData->filename(i))
                (*fileList)[i]=XP_STRDUP(urlData->filename(i));
            else
                (*fileList)[i]=NULL;
        }
        
        // save desktop tmp file data  for cleanup at end of drag
        _desktopFileData=(XFE_DesktopType*)urlData;

        XtFree(stringData);
    }

    return;
}

const char *XFE_DragNetscape::getTargetFTRType(Atom target)
{
    if (target==_XA_NETSCAPE_URL) return XFE_URLDesktopType::ftrType();
    
    return DEFAULT_FTR_TYPE;
}


void XFE_DragNetscape::dragDropFinishCb()
{
    XFE_DragDesktop::dragDropFinishCb();

    // cleanup temporary directory and files used for Netscape data
    if (_desktopFileData) {
        _desktopFileData->cleanupDataFiles();
        delete _desktopFileData;
        _desktopFileData=NULL;
    }
}


//
// XFE_DropBase
//

//
// callback stubs
//

void XFE_DropBase::DragProc(Widget w, XtPointer,XtPointer cb)
{
    XtPointer ud;
    XtVaGetValues(w,XmNuserData,&ud,NULL);
    XFE_DropBase *d=(XFE_DropBase*)ud;
    if (d)
        d->dragProc((XmDragProcCallbackStruct*)cb);
}


void XFE_DropBase::DropProc(Widget w,XtPointer,XtPointer cb) {
    XtPointer ud;
    XtVaGetValues(w,XmNuserData,&ud,NULL);
    XFE_DropBase *d=(XFE_DropBase*)ud;
    if (d)
        d->dropProc((XmDropProcCallbackStruct*)cb);
}

void XFE_DropBase::DestroyCb(Widget,XtPointer cd,XtPointer) {
    XFE_DropBase *d=(XFE_DropBase*)cd;
    if (d)
        d->dropComplete();
}

void XFE_DropBase::TransferProc(Widget w, XtPointer cd, Atom */*seltype*/, Atom *type,
                            XtPointer value, unsigned long *length, int */*format*/)
{
#if defined(DEBUG_tao)
	printf("\nXFE_DropBase::TransferProc, value=%x\n", value);
#endif
    XFE_DropBase *d=(XFE_DropBase*)cd;
    if (d)
        d->transferProc(w,*type,value,(unsigned int)*length);
}


// constructor

XFE_DropBase::XFE_DropBase(Widget widget)
{
    _widget=widget;
    _operations=(unsigned int)XmDROP_NOOP;
    _targets=NULL;
    _numTargets=0;
    _dropEventX=0;
    _dropEventY=0;
    _chosenTarget=None;

    InitializeDisplayInfo(widget);
    
    // create drop site, disabled by default
#ifdef DROP_ENABLED    
    Arg args[20];
    int n=0;
    XtSetArg(args[n],XmNdragProc,DragProc); n++;
    XtSetArg(args[n],XmNdropProc,DropProc); n++;
    XtSetArg(args[n],XmNdropSiteActivity,XmDROP_SITE_INACTIVE);n++;
    XtSetArg(args[n],XmNdropSiteType,XmDROP_SITE_COMPOSITE);n++;
    
    XmDropSiteRegister(_widget, args, n);
#endif
    // make XFE_DropBase available to DragProc, DropProc
    XtVaSetValues(widget,XmNuserData,this,NULL);

    // initialize destroy callback struct
    _destroyCbList[0].callback=DestroyCb;
    _destroyCbList[0].closure=(XtPointer)this;
    _destroyCbList[1].callback=NULL;
    _destroyCbList[1].closure=NULL;
}


// destructor

XFE_DropBase::~XFE_DropBase()
{
#ifdef DROP_ENABLED
	//
	//    Don't do this, because XmDropSiteRegister() sets up a destroy
	//    callback that does this (well the equivelent). If it's called twice,
	//    we crash in _XmIEndUpdate()....djw
#if 0
    if (_widget)
        XmDropSiteUnregister(_widget);
#endif
#endif
    freeTargetList();
}

// convenience fn - allow easy cleanup of old targets

void XFE_DropBase::freeTargetList()
{
    if (_targets) {
        delete [] _targets;
        _targets=NULL;
    }
    _numTargets=0;
}


// drop site management

void XFE_DropBase::enable()
{
    // enable site
#ifdef DROP_ENABLED    
    Arg args[1];
    XtSetArg(args[0],XmNdropSiteActivity,XmDROP_SITE_ACTIVE);
    XmDropSiteUpdate(_widget,args,1);
#endif
    // set targets and operations
    // (can't do it in constructor because targets() and operations() are virtual)
    update();
}


void XFE_DropBase::disable()
{
#ifdef DROP_ENABLED    
    Arg args[1];
    XtSetArg(args[0],XmNdropSiteActivity,XmDROP_SITE_INACTIVE);
    XmDropSiteUpdate(_widget,args,1);
#endif    
}

void XFE_DropBase::update()
{
    // allow derived class to change attributes
    operations();
    freeTargetList();
    targets();
    
    // apply changes to drop site
#ifdef DROP_ENABLED    
    Arg args[20];
    int n=0;
    XtSetArg(args[n], XmNimportTargets, _targets); n++;
    XtSetArg(args[n], XmNnumImportTargets, _numTargets); n++;
    XtSetArg(args[n], XmNdropSiteOperations, _operations); n++;
    XmDropSiteUpdate(_widget,args,n);
#endif    
}

void XFE_DropBase::update(ArgList args,Cardinal n)
{
    // apply target/operation changes to drop site
	update();

    // apply user defined changes to drop site
#ifdef DROP_ENABLED    
    XmDropSiteUpdate(_widget,args,n);
#endif    

}

// override to set _operations

void XFE_DropBase::operations()
{
    _operations=(unsigned int) (XmDROP_MOVE | XmDROP_COPY);
}

// override to set _targets

void XFE_DropBase::targets()
{
    freeTargetList();

    _numTargets=1;
    _targets=new Atom[_numTargets];

    _targets[0]=XA_STRING;
}

// override to add drag-in feature
void XFE_DropBase::dragIn()
{
    XDEBUG(printf("XFE_DropBase::dragIn(%d,%d)\n",_dropEventX,_dropEventY));
}

// override to add drag-out feature

void XFE_DropBase::dragOut()
{
    XDEBUG(printf("XFE_DropBase::dragOut(%d,%d)\n",_dropEventX,_dropEventY));
}

// override to add drag-motion feature
void XFE_DropBase::dragMotion()
{
    // XDEBUG(printf("XFE_DropBase::dragMotion(%d,%d)\n",_dropEventX,_dropEventY));
}

// override to add operation-changed feature

void XFE_DropBase::operationChanged()
{
}

// override to add validation of drop targets

Atom XFE_DropBase::acceptDrop(unsigned int dropOperation,Atom *dropTargets,unsigned int numDropTargets)
{
    // pick first matching target in class's list, if operation is valid
    
    if (!(dropOperation & _operations))
        return None;

    Atom selected=None;
    
    XDEBUG(printf("TARGETS:\n"));
#if defined(DEBUG_tao)
	printf("\nXFE_DropBase::acceptDrop\n");
	for (int ii=0; ii < _numTargets; ii++)
		printf("\n_targets[%d]=%s", 
			   ii, XGetAtomName(XtDisplay(_widget),_targets[ii]));
	for (int jj=0; jj < numDropTargets; jj++)
		printf("\ndropTargets[%d]=%s", 
			   jj, XGetAtomName(XtDisplay(_widget),dropTargets[jj]));
#endif
    for (int i=0;i<_numTargets; i++) {
        for (int j=0; j < (int)numDropTargets; j++) {
             XDEBUG(if (i==0) printf("  %s\n",XGetAtomName(XtDisplay(_widget),dropTargets[j])));
            if (_targets[i]==dropTargets[j]) {
                selected=dropTargets[j];
                break;
            }
        }
        if (selected!=None)
            break;
    }

    return selected;
}


// override to tidy up after drop

void XFE_DropBase::dropComplete()
{
}

// callback methods

void XFE_DropBase::dragProc(XmDragProcCallbackStruct *cb)
{
    // record location of drop:
    _dropEventX=cb->x;
    _dropEventY=cb->y;

    switch(cb->reason) {
        case XmCR_DROP_SITE_ENTER_MESSAGE:
            // verify that this combo of operation and targets is acceptable
            if (cb->operation!=XmDROP_NOOP &&
                acceptDropWrapper(cb->operation,cb->dragContext)!=None) {
                cb->dropSiteStatus = XmVALID_DROP_SITE;
                dragIn();
            }
            else {
                cb->dropSiteStatus = XmINVALID_DROP_SITE;
            }
            break;
        case XmCR_DROP_SITE_LEAVE_MESSAGE:
            dragOut();
            break;
        case XmCR_DROP_SITE_MOTION_MESSAGE:
            dragMotion();
            break;
        case XmCR_OPERATION_CHANGED:
            operationChanged();
            break;
        default:
            cb->dropSiteStatus = XmINVALID_DROP_SITE;
            break;
    }

    // allow animation to be performed
    cb->animate = True;
}



void XFE_DropBase::dropProc(XmDropProcCallbackStruct *cb)
{
    if (cb->dropAction != XmDROP_HELP) {
        handleDrop(cb);
    }
    else {
        // option to display help dialog - we just cancel drag
        Arg args[10];
        int n=0;
        cb->dropSiteStatus=XmINVALID_DROP_SITE;
        XtSetArg(args[n],XmNtransferStatus,XmTRANSFER_FAILURE); n++;
        XtSetArg(args[n],XmNnumDropTransfers,0); n++;
        XmDropTransferStart(cb->dragContext,args,n);
        return;        
    }
}

void XFE_DropBase::handleDrop(XmDropProcCallbackStruct *cb)
{
    Arg args[10];
    int n;

    // record location of drop:
    _dropEventX=cb->x;
    _dropEventY=cb->y;
    
    // Cancel the drop on invalid drop operations

    if (cb->operation==XmDROP_NOOP) {
        n = 0;
        cb->dropSiteStatus = XmINVALID_DROP_SITE;
        XtSetArg(args[n], XmNtransferStatus, XmTRANSFER_FAILURE); n++;
        XtSetArg(args[n], XmNnumDropTransfers, 0); n++;
        XmDropTransferStart(cb->dragContext, args, n);
        return;
    }

    // select target based on operation and available targets
    // (remember chosen target for use later in tranfer proc. naughty
    // CDE file mgr doesn't give the target it was asked for.)
    
    _chosenTarget=acceptDropWrapper(cb->operation,cb->dragContext);
    
    if (_chosenTarget==None) { // transfer not valid
        n = 0;
        cb->operation = (unsigned char)XmDROP_NOOP;
        cb->dropSiteStatus = XmINVALID_DROP_SITE;
        XtSetArg(args[n], XmNtransferStatus, XmTRANSFER_FAILURE); n++;
        XtSetArg(args[n], XmNnumDropTransfers, 0); n++;
        XmDropTransferStart(cb->dragContext, args, n);
        return;
    }
    
    // start transfer

    XmDropTransferEntryRec transfers[2];
    Cardinal numTransfers = 1;
    transfers[0].target = _chosenTarget;
    transfers[0].client_data=(XtPointer)this;

    // send DELETE to drag source if this is a move
    if (cb->operation == XmDROP_MOVE) {
        transfers[1].target=_XA_DELETE;
        transfers[1].client_data=(XtPointer)this;
        numTransfers+=1;
    }

        
    n = 0;
    cb->dropSiteStatus = XmVALID_DROP_SITE;
    XtSetArg(args[n], XmNdropTransfers, transfers); n++;
    XtSetArg(args[n], XmNnumDropTransfers, numTransfers); n++;
    XtSetArg(args[n], XmNdestroyCallback, _destroyCbList); n++;
    XtSetArg(args[n], XmNtransferProc, TransferProc); n++;
    XmDropTransferStart(cb->dragContext, args, n);
}


void XFE_DropBase::transferProc(Widget dragContext,Atom type, XtPointer value,unsigned int length)
{
    // transfer data via dropTransfer()

    XDEBUG(printf("XFE_DropBase::transferProc(%s)\n",type ? XmGetAtomName(XtDisplay(_widget),type):"(null)"));

    // check for response to a DELETE message - no action required
    if (type==None || type==_XA_NULL) {
        return;
    }
    
    if (value && length>0) {
        if (isFileTarget(_chosenTarget) &&
            (type==_chosenTarget || type==XA_STRING)) {
            // process file targets in XFE_DropDesktop
            // (test for XA_STRING is a hack to deal with CDE 1.0 bug, where
            // dtfile returns STRING data in response to request for FILE_NAME.)
            if (processFileTargetWrapper(_chosenTarget,value,length))
                return; // success
        }
        else {
            // package up item into drop list and pass to derived class method
            Atom *targets=new Atom[1];
            const char **dropData=new const char*[1];
            char *dropInfo=new char[length+1];
            memcpy(dropInfo,(char*)value,length);
            dropInfo[length]='\0';
            targets[0]=type;
            dropData[0]=dropInfo;
            int dropStatus=processTargets(targets,dropData,1);
            delete [] targets;
            delete dropData;
            delete dropInfo;

            if (dropStatus)
                return; // success
        }    
    }
    else {
        // transfer failed
        XtVaSetValues(dragContext,
                      XmNtransferStatus, XmTRANSFER_FAILURE,
                      XmNnumDropTransfers, 0,
                      NULL);
    }
    
    // nyi - is this right? doesn't Xt/Xm free this?, why not free above?
    if (value)
        XtFree((char*)value);
}

// wrapper around virtual function acceptDrop()
// (hide Motif cruft from dervived class)

Atom XFE_DropBase::acceptDropWrapper(unsigned int op,Widget dragContext)
{
    Atom *exportTargets=NULL;
    Cardinal numExportTargets=0;
    XtVaGetValues(dragContext,
                  XmNexportTargets, &exportTargets,
                  XmNnumExportTargets, &numExportTargets,
                  NULL);
    if (exportTargets && numExportTargets>0)
        return acceptDrop(op,exportTargets,numExportTargets);
    else
        return None;
}


//
// XFE_DropDesktop
//

// constructor

XFE_DropDesktop::XFE_DropDesktop(Widget widget) : XFE_DropBase(widget)
{
}


// destructor

XFE_DropDesktop::~XFE_DropDesktop()
{
}

//
// methods below override XFE_DropBase defaults
//

// desktop file types we understand
int XFE_DropDesktop::isFileTarget(Atom target)
{
    return (target==_XA_SGI_ICON ||
            target==_XA_SGI_FILE ||
            target==_XA_SGI_URL ||
            target==_XA_FILE_NAME)
        ? TRUE : FALSE;
}

void XFE_DropDesktop::acceptFileTargets()
{
    if (!_targets || _numTargets==0)
        return;

    // insert desktop file dnd atoms into list
    // must insert before generic STRING - some desktops (IRIX 6.3+)
    // will provide filename or URL as STRING - we want all the info
    Atom *newTargets=new Atom[_numTargets+5];
    int i=0;
    int ni=0;
    int added=FALSE;
    while (i<_numTargets) {
        if (_targets[i]==XA_STRING) {
            newTargets[ni++]=_XA_SGI_URL;
            newTargets[ni++]=_XA_SGI_ICON;
            newTargets[ni++]=_XA_SGI_FILE;
            newTargets[ni++]=_XA_FILE_NAME;
            newTargets[ni++]=_XA_TARGETS; // debugging hack - force display of all targets
            added=TRUE;
        }
        newTargets[ni++]=_targets[i++];
    }
    if (!added) {
        // no STRING in targets list, so append
        newTargets[ni++]=_XA_SGI_URL;
        newTargets[ni++]=_XA_SGI_ICON;
        newTargets[ni++]=_XA_SGI_FILE;
        newTargets[ni++]=_XA_FILE_NAME;
        newTargets[ni++]=_XA_TARGETS; // debugging hack - force display of all targets
    }
    
    delete [] _targets;
    _targets=newTargets;
    _numTargets=ni;
}

// process dropped data

int XFE_DropDesktop::processFileTargetWrapper(Atom type,XtPointer value,unsigned int length)
{
    if (!value || length==0)
        return FALSE;

    int dropStatus=FALSE;
    
    char *dropInfo=new char[length+1];
    memcpy(dropInfo,(char*)value,length);
    dropInfo[length]='\0';
    
    if (type==_XA_SGI_URL)
        dropStatus=processSGI_URL(dropInfo,length);
    else if (type==_XA_SGI_FILE)
        dropStatus=processFILE_NAME(dropInfo,length);
    else if (type==_XA_SGI_ICON)
        dropStatus=processSGI_ICON(dropInfo,length);
    else if (type==_XA_FILE_NAME)
        dropStatus=processFILE_NAME(dropInfo,length);

    delete dropInfo;
    
    return dropStatus;
}


int XFE_DropDesktop::processSGI_URL(char *dropInfo,unsigned int /*length*/)
{
    // parse url and title out of SGI_URL drop info
    
    char *url=NULL;
    char *title=NULL;

    // url
    char *urlStart=strstr(dropInfo,"SRC=\"");
    if (urlStart) {
        urlStart+=5;
        char *end=urlStart;
        while (*end && *end!='\"') // look for closing quotes
            end++;
        int len=end-urlStart;
        if (len>0) {
            url=new char[len+1];
            strncpy(url,urlStart,len);
            url[len]='\0';
        }
    }

    // title
    char *titleStart=strstr(dropInfo,"TITLE=\"");
    if (titleStart) {
        titleStart+=7;
        char *end=titleStart;
        while (*end && !(*end=='\"' && *(end-1)!='\\')) // look for non-escaped closing quotes
            end++;
        int len=end-titleStart;
        if (len>0) {
            title=new char[len+1];
            strncpy(title,titleStart,len);
            title[len]='\0';
        }
    }

    // should this be a list, separated by '\0'?
    int dropStatus=processDesktopURLTarget(title,url);
    
    if (url)
        delete url;
    if (title)
        delete title;

    return dropStatus;
}


int XFE_DropDesktop::processSGI_ICON(char *dropInfo,unsigned int length)
{
    const int FTR_MAX_SIZE=128; // from ftr header files
    const char SGI_ICON_FORMAT[] =
        "Category:%s Name:%s Type:%s Host:%s ViewX:%d ViewY:%d ViewGallery:%d";
    
    const int MAX_FILES=200;
    char *files[MAX_FILES];
    int numFiles=0;

    // parse individual files out of _SGI_ICON drop info
    
    char *start=dropInfo;;
    while (start<dropInfo+length && numFiles<MAX_FILES) {
        if (strlen(start)>0) {

            // parse _SGI_ICON data using standard format
            char filename[MAXPATHLEN]="";
            char ftrname[FTR_MAX_SIZE]="";
            char category[FTR_MAX_SIZE]="";
            char hostName[MAXHOSTNAMELEN]="";
            int viewx=0, viewy=0, gal=0;
            
            int numFields=sscanf(start, SGI_ICON_FORMAT,
                               category, filename, ftrname,
                               hostName, &viewx, &viewy, &gal);
            
            // ignore files located on remote hosts
            // (test strips off domain name, uses only host name)
            char *dot;
            if (dot=strchr(hostName,'.'))
                *dot='\0';
            if (strcmp(hostName,localHostName)==0) {
                // copy into file list
                files[numFiles++]=strdup(filename);
            }
        }
        
        start+=strlen(start)+1;         // move to next file     
    }

    int dropStatus=processFileTarget((const char**)files,numFiles);

    // free file name strings and list
    for (int i=0;i<numFiles;i++)
        if (files[i]) free((void*)files[i]);

    return dropStatus;
}

int XFE_DropDesktop::processFILE_NAME(char *dropInfo,unsigned int length)
{
    const int MAX_FILES=200;
    char *files[MAX_FILES];
    int numFiles=0;
    int dropStatus=FALSE;

    // parse individual files out of drop info

    char *start=dropInfo;;
    while (start<dropInfo+length && numFiles<MAX_FILES) {
        // detect end of chunk, separated by null
        char *end=start;
    	while(*end!='\0' && end<dropInfo+length)
    	    end++;
        *end='\0';  // ensure this chunk is terminated
        
        if (strlen(start)>0) {
            if (NET_URL_Type(start)==0) {
                files[numFiles++]=start;       // add to file drop list
            }
        }
        
        start=end+1;     
    }
    
    if (processFileTarget((const char**)files,numFiles))
        dropStatus=TRUE;
    
    return dropStatus;
}

//
// XFE_DropNetscape
//

// constructor

XFE_DropNetscape::XFE_DropNetscape(Widget widget) : XFE_DropDesktop(widget)
{
}


// destructor

XFE_DropNetscape::~XFE_DropNetscape()
{
}

//
// methods below override XFE_DropDesktop defaults
//

int XFE_DropNetscape::processFileTarget(const char**files,int numFiles)
{
    XDEBUG(printf("XFE_DropNetscape::processFileTarget()\n"));

    Atom *targets=new Atom[numFiles];
    const char **data=new const char*[numFiles];
    const int MAX_LENGTH=4000;
    char line[MAX_LENGTH+1];
    line[MAX_LENGTH]='\0';

    int i;

    // build list of dropped items
    for (i=0;i<numFiles;i++) {
        // default is regular file
        targets[i]=_XA_FILE_NAME;
        data[i]=files[i];
        
        // if it's a Netscape desktop file, extract the data
        FILE *fp;
        if (fp=fopen(files[i],"r")) {
            line[0]='\0'; // ensure string will be null-terminated
            fgets(line,MAX_LENGTH,fp);

            // NetscapeURL
            if (XFE_URLDesktopType::isDesktopType(line)) {
                targets[i]=_XA_NETSCAPE_URL;
                XFE_URLDesktopType urlData(fp);
                const char *str=urlData.getString();    
                data[i]=(str ? XP_STRDUP(str) : 0);
            }
            
            // nyi - check for other Netscape desktop types here
            // (are no others..  yet.)
            fclose(fp);
        }
    }

    int dropStatus=processTargets(targets,data,numFiles);

    // free our copy of non-file data (FILE_NAME's were not copied)
    for (i=0;i<numFiles;i++) {
        if (targets[i]!=_XA_FILE_NAME)
            if (data[i])
                XP_FREE((void*)data[i]);
    }
    
    delete [] targets;
    delete data;
    
    return dropStatus;
}

//
// translate desktop URL type into a NetscapeURL
//
int XFE_DropNetscape::processDesktopURLTarget(const char *title,const char *url)
{
    XDEBUG(printf("XFE_DropNetscape::processDesktopURLTarget(%s,%s)\n",title,url));

    if (!url)
        return FALSE;
    
    Atom targets[1];
    const char *data[1];

    targets[0]=_XA_NETSCAPE_URL;
    XFE_URLDesktopType urlData;
    urlData.createItemList(1);
    urlData.url(0,url);
    if (title) urlData.title(0,title);
    
    const char *str=urlData.getString();
    if (str) {
        data[0]=str;
        int dropStatus=processTargets(targets,data,1);
	// nyi - when we add more data, do cleanup here.
        return dropStatus;
    }
    else {
        return FALSE;
    }

    // nyi - delete data and/or contents?
}


