    Drop files on nedit to open them

    This is Scott Tringali's patch for allowing NEdit to accept files dropped
    from a file manager. You can find it here:

    https://sourceforge.net/tracker/?func=detail&atid=311005&aid=1371164&group_id=11005
	    [ 1371164 ] Drop files on nedit to open them
	    dropfile.patch 2005-12-01 11:49

    His comments:

        Drop files on to nedit from a file manager to load them.  This is just a
        proof of concept, and needs lots of work.  Specifically:

          - 1) We don't do any real URI decoding.  Files with spaces in the
               names won't load.

          - 2) Error checking - users can attempt to drag files located on
               different machines, and we should probably ignore those files
               instead of aborting.

          - 3) Protocol checking - right now, all we use is "file:" but it may
               be possible to support others.

          - 4) Badly formed URIs - different apps formulate URIs differently and
               incorrectly.  See comment in patch.

          - 5) Errors posted during a drag will wind up with a dialog under the
               drag icon.  Not good.

          - 6) Handle multiple files - seems okay for text/uri-list (nautilus)
               but not done for FILE_NAME (CDE).

          - 7) What other atom types should we support?  What do other apps
               provide?

          - 8) Better awareness of windows and tabs (and prefs) if need be. 
               Right now the drop site is the entire application.

        I've tried it out with CDE dtfile on tru64, and Nautilus under Fedora
        Core 2.

    2006-09-21

        Corrected to fix a compiler warning.

diff -aur nedit_official nedit_mod
diff -aur nedit_official/source/window.c nedit_mod/source/window.c
--- nedit_official/source/window.c	2008-01-05 03:24:13.000000000 +0100
+++ nedit_mod/source/window.c	2008-01-14 16:52:07.000000000 +0100
@@ -107,6 +107,9 @@
 #include <Xm/PrimitiveP.h>
 #include <Xm/Frame.h>
 #include <Xm/CascadeB.h>
+#include <Xm/AtomMgr.h>
+#include <Xm/DragDrop.h>
+
 #ifdef EDITRES
 #include <X11/Xmu/Editres.h>
 /* extern void _XEditResCheckMessages(); */
@@ -190,6 +193,12 @@
 static void cloneTextPanes(WindowInfo *window, WindowInfo *orgWin);
 static UndoInfo *cloneUndoItems(UndoInfo *orgList);
 static Widget containingPane(Widget w);
+static void registerFileDropSite(Widget new);
+static void dropCB(Widget w, XtPointer client_data, XtPointer call_data);
+static void transferCB(Widget w, XtPointer client_data, Atom *selType,
+	Atom *type, XtPointer value, unsigned long length, int format);
+static void transferUriListCB(Widget w, XtPointer client_data, Atom *selType,
+	Atom *type, XtPointer value, unsigned long length, int format);
 
 static WindowInfo *inFocusDocument = NULL;  	/* where we are now */
 static WindowInfo *lastFocusDocument = NULL;	    	/* where we came from */
@@ -386,6 +395,7 @@
     XtSetArg(al[ac], XmNuserData, window); ac++;
     mainWin = XmCreateMainWindow(winShell, "main", al, ac);
     window->mainWin = mainWin;
+    registerFileDropSite(mainWin);
     XtManageChild(mainWin);
     
     /* The statsAreaForm holds the stats line and the I-Search line. */
@@ -4744,3 +4754,200 @@
 		ExposureMask, (XEvent *)&ev);
     }
 }    
+
+/*
+** Mark this widget as accepting drops of certain types
+*/
+static void registerFileDropSite(Widget new)
+{
+    Arg args[5];
+    Atom targets[2];
+    int n = 0;
+    
+    /* CDE dtfile */
+    targets[0] = XmInternAtom(XtDisplay(new), "FILE_NAME", False);
+    /* GNOME nautilus */
+    targets[1] = XmInternAtom(XtDisplay(new), "text/uri-list", False);
+    
+    XtSetArg(args[n], XmNdropProc, dropCB); n++;
+    XtSetArg(args[n], XmNnumImportTargets, XtNumber(targets)); n++;
+    XtSetArg(args[n], XmNimportTargets, targets); n++;
+    XtSetArg(args[n], XmNdropSiteOperations, XmDROP_COPY); n++;
+    /* Composite is necessary since some child widgets (text, scrollbar??)
+       register themselves as drop sites. */
+    XtSetArg(args[n], XmNdropSiteType, XmDROP_SITE_COMPOSITE); n++;
+
+    XmDropSiteRegister(new, args, n);
+}
+
+/*
+** Validate a drop request.  The source will tell us what types it supports.
+** We pick one that we like, and then start the transfer.
+*/
+static void dropCB(Widget w, XtPointer client_data, XtPointer call_data)
+{
+    XmDropProcCallback drop = call_data;
+    int n = 0;
+    Arg args[3];
+
+    XtSetArg(args[n], XmNtransferStatus, XmTRANSFER_FAILURE); n++;
+    XtSetArg(args[n], XmNnumDropTransfers, 0); n++;
+
+    if (drop->dropAction == XmDROP)
+    {   
+        const Atom fileNameAtom = XmInternAtom(XtDisplay(w), "FILE_NAME", False);
+        const Atom uriAtom = XmInternAtom(XtDisplay(w), "text/uri-list", False);
+        XmDropTransferEntryRec xfers[1];
+        Cardinal ntargets;
+        Atom *exportTargets;
+        int i = 0;
+
+        XtVaGetValues(drop->dragContext,
+                      XmNexportTargets, &exportTargets,
+                      XmNnumExportTargets, &ntargets,
+                      NULL);
+
+        /* TODO: handle multiple file drops. */
+        for (i = 0; i < ntargets; i++)
+        {
+            fprintf(stderr, "Source offers type %s\n", 
+                    XGetAtomName(TheDisplay, exportTargets[i]));
+
+            if (exportTargets[i] == uriAtom)
+            {
+                xfers[0].target = uriAtom;
+                xfers[0].client_data = WidgetToWindow(w);
+                n = 0;
+                XtSetArg(args[n], XmNtransferProc, transferUriListCB); n++;
+                XtSetArg(args[n], XmNnumDropTransfers, 1); n++;
+                XtSetArg(args[n], XmNdropTransfers, xfers); n++;
+                break;
+            }
+            else if (exportTargets[i] == fileNameAtom)
+            {
+                xfers[0].target = fileNameAtom;
+                xfers[0].client_data = WidgetToWindow(w);
+                n = 0;
+                XtSetArg(args[n], XmNtransferProc, transferCB); n++;
+                XtSetArg(args[n], XmNnumDropTransfers, 1); n++;
+                XtSetArg(args[n], XmNdropTransfers, xfers); n++;
+                break;
+            }
+
+        }
+    }
+
+    /* Now that we've chosen the target type, do the transfer. */
+    XmDropTransferStart(drop->dragContext, args, n);
+}
+
+/*
+** Execute the drop request.
+*/
+static void transferCB(Widget w,
+                       XtPointer client_data,
+                       Atom *selType,
+                       Atom *type,
+                       XtPointer value,
+                       unsigned long length,
+                       int format)
+{
+    char filename[MAXPATHLEN];
+    char pathname[MAXPATHLEN];
+    WindowInfo *win = client_data;
+    const char *nameText = value;
+
+    fprintf(stderr, "selType = %s, type = %s, value = %s\n",
+            XGetAtomName(TheDisplay, *selType),
+            XGetAtomName(TheDisplay, *type),
+            nameText);
+
+    if (*type != XA_STRING)
+        return;
+        
+    if (ParseFilename(nameText, filename, pathname) != 0)
+    {
+        XBell(TheDisplay, 0);
+       return;
+    } 
+
+    /* Review this for tab preferences */
+    EditExistingFile(win, filename, pathname, 0, NULL, False, NULL, True, False);
+}
+
+/*
+** Execute the drop request.
+*/
+static void transferUriListCB(Widget w,
+                              XtPointer client_data,
+                              Atom *selType,
+                              Atom *type,
+                              XtPointer value,
+                              unsigned long length,
+                              int format)
+{
+    char filename[MAXPATHLEN];
+    char pathname[MAXPATHLEN];
+    WindowInfo *win = client_data;
+    char *uri = value;
+
+    const Atom uriAtom = XmInternAtom(XtDisplay(w), "text/uri-list", False);
+
+    fprintf(stderr, "selType = %s, type = %s, value = %s\n",
+            XGetAtomName(TheDisplay, *selType),
+            XGetAtomName(TheDisplay, *type),
+            uri);
+
+    if (*type != uriAtom)
+        return;
+
+    while (uri != NULL && *uri != 0)
+    {
+       /*  URI lists should look like this:
+
+             file:///path/to/foo\r\n
+             file:///path/to/bar\r\n
+       
+          Terrible hack; we need some real URI decoding here.  This won't
+          work for file names with embedded spaces, just for starters. 
+          Also note that we're likely to get malformed URIs, see:
+
+          http://www.ip97.com/phpgtk/tutorials.filednd.urilist.html
+          http://www.newplanetsoftware.com/xdnd/dragging_files.html
+          http://freedesktop.org/wiki/Draganddropwarts
+
+        */
+
+        static const char *prefix = "file://";
+        static const char *suffix = "\r\n";
+
+        /* Peel off the prefix and suffix to get at the (creamy nougat)
+           filename inside. */
+
+        if (strstr(uri, prefix) == uri)
+        {
+            char *end = strstr(uri, suffix);
+
+            if (end != 0)
+                *end = '\0';
+
+            if (ParseFilename(uri+strlen(prefix), filename, pathname) != 0)
+            {
+                XBell(TheDisplay, 0);
+               return;
+            } 
+
+            /* Review this for tab preferences */
+            EditExistingFile(win, filename, pathname, 0, NULL, False, NULL, True, False);
+
+            if (end != 0)
+                uri = end + strlen(suffix);
+        }
+        else
+        {
+            fprintf(stderr, "Unknown URI protocol: %s\n", uri);
+            /* XXX: Should just skip this entry instead of aborting */
+            uri = 0;
+        }
+    }
+}
