On Mon, Dec 7, 2009 at 6:16 PM, Denis S. Fokin <[email protected]> wrote:
> Hi Damjan,

Hello again Denis :-)

> just to make things easer.
> From the CR description I see that the submitter requests a couple of
> things.
>
> - to create a Transferable on the fly (it is possible right now)
> - to publish a Transferable as a native stream (it seems as a pretty
> independent task)

Transfer data, not Transferable.

> - to add a callback function to notify the source application that the drop
> operation has succeeded and that the promised DataFlavor must now be
> produced

That's more of a possible mechanism than a policy.

>
> It seems, that the last item could be implemented as a XdndActionAsk action
> from XDnD protocol.
>
> Do you agree that this request could be divided in these three parts?

Not exactly.

Maybe an example is best. I'm attaching some drop-side sample code and
a patch I've written that gets the XDS (X11 direct save) protocol
working.

Basically in XDS the drop target first tells the drag source where to
save the files using a window property, and requesting the
XdndDirectSave0 format's data from the XSelection only starts the
transfer and returns the result. So I've had to abstract it so that
getting the Transferable's transfer data doesn't actually fetch the
data from the drag source yet, but instead returns an object on which
setting the save location causes the drag source's window property to
be set and only then we request the data, causing the files to be
written and the status reported. The other direction - when Java is
the drag source - is equally fun: we have to save files and update
window properties during the Transferable conversion and only return
the result as the actual transfer data.

As usual, Windows has to be radically different, so sadly I expect the
majority of this patch will end up dedicated to IStream <->
j.i.InputStream interop, VARIANTs with workarounds for Windows
Explorer bugs, and other COM escapades. I've also chosen specific
forms of transfer data so they will be able to work on Windows too.

So far I've used Nautilus to test with as the drop target, and File
Roller as the drag source. The patch works well against them.

> Thank you,
>        Denis.

Thank you
Damjan

>
>
> Damjan Jovanovic wrote:
>>
>> Hi
>>
>> Is anybody working on this bug
>> (http://bugs.sun.com/view_bug.do?bug_id=4808793)?
>>
>> If not I'd like to have a go at it. My idea is described below.
>>
>> java.awt.datatransfer.DataFlavor.javaFileListFlavor has drop target
>> pull semantics, with the drop target being responsible for the
>> operation on the files. It needs the files to already exist. It's
>> essentially a "take these files and use them" action. For example, if
>> you drag files into a Java SVN client, the SVN client could check them
>> in. These semantics work when the drop target is not the local
>> filesystem.
>>
>> But these semantics are exactly the opposite of what's needed when the
>> files in the drag source are not actual files from the local
>> filesystem - for example they are files in an FTP server or even
>> virtual files that don't exist yet. For this we need drag source push
>> semantics, with the drop target either telling the drag source where
>> to save the files, or using some sort of protocol to move the file
>> contents from the drag source to wherever they are to be saved or
>> used.
>>
>> Windows, X11 and MacOS all support this latter option, and while it is
>> a less commonly used file drag scenario, some important applications
>> require it (eg. Microsoft Outlook) and others support it among other
>> possible formats (eg. Windows Explorer, GIMP, File Roller). The bug
>> has been open since early 2003 and has 60 votes; the drag from
>> Microsoft Outlook to Java bug
>> (http://bugs.sun.com/view_bug.do?bug_id=6242241) which relies on this
>> bug has another 20 votes.
>>
>> Windows has the CFSTR_FILEDESCRIPTOR/CFSTR_FILECONTENTS formats for
>> this. Basically files are presented using their contents, in the form
>> of IStream, IStorage or HGLOBAL, with additional data describing the
>> file name and properties
>> (http://msdn.microsoft.com/en-us/library/bb776902(VS.85).aspx).
>>
>> OS X has "promise data flavors"
>>
>> (http://developer.apple.com/documentation/Carbon/Reference/Pasteboard_Reference/Reference/reference.html).
>> The drag source supplies a promise instead of data, and a callback
>> that's invoked to supply the data. When the callback is called, it can
>> get a URL where the files should be saved.
>>
>> X11 has the X direct save protocol
>> (http://www.freedesktop.org/wiki/Specifications/XDS), which seems to
>> be a mixture of the Windows and OS X ways. The emphasis in the
>> protocol is on saving a file, rather than transferring a list of
>> files. First the drop target gives the drag source a URL where to save
>> the file(s), just like OS X would. If the drag source cannot access
>> that URL or doesn't have permission, it reports this to the drop
>> target, and the drop target then falls back to requesting the file
>> contents as application/octet-stream - similar to the Windows way, but
>> it only works for one file.
>>
>> Obviously there are big differences on these platforms that AWT has to
>> unify. Here is a proposal of how.
>>
>> Drop target
>> We add a new DataFlavor, called FileDropReferralFlavor. When the drop
>> target asks for this flavor, it gets back an object called
>> FileDropReferral, and must call a "public void setFileDropLocation(URL
>> location)" method to tell the drag source where it should save the
>> files. The file transfer proceeds asynchronously, and potentially
>> within the JVM (ie. the application shouldn't exit if it has
>> outstanding transfers). After the transfer is complete, some event to
>> that effect is sent to the drop application's EDT, maybe to a listener
>> registered with the FileDropReferral.
>>
>> Drag source
>> The drag source uses a different DataFlavor to provide its files as
>> streams, say AsyncFileStreamsFlavor. After the drop it provides an
>> enumeration that can be used to lazy open InputStream objects, and
>> some kind of header at least naming each stream.
>>
>> Implementation:
>> On all platforms, when Java is the drag source it spawns a separate
>> thread where possible, and writes the streams into the drop target or
>> URL. When Java is the drop target, on Windows, it spawns a separate
>> thread where possible and reads in the streams and saves them to the
>> target URL that the drop target set, while on X11 and MacOS it just
>> provides the target URL to the drag source and lets the drag source do
>> the saving.
>>
>> These interfaces are unintuitive, asymmetric and ugly. But then again
>> desktops in different operating systems have given file transfers
>> little real thought so there isn't much better we can do now, and it's
>> still vastly uglier to do these in native code (using COM or low-level
>> X11 - yuck!) than in Java. Also this is an advanced DataFlavor, which
>> should be used by a minority of applications, and only in special
>> cases where javaFileListFlavor doesn't fit.
>>
>> Finally, GTK+ still doesn't even have an API for this, SWT is
>> struggling and only works on Windows, and even Microsoft's .NET
>> doesn't support this
>> (http://www.pcreview.co.uk/forums/thread-1854594.php) so this is a
>> real opportunity for Java+AWT/Swing to be a shining example and lead
>> the way.
>>
>> Any thoughts before I start hacking?
>>
>> Regards
>> Damjan
>>
>
>
diff -r ca34cfff70a4 src/share/classes/java/awt/datatransfer/AbstractFile.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/java/awt/datatransfer/AbstractFile.java	Mon Dec 07 20:05:03 2009 +0200
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2009 Damjan Jovanovic and Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package java.awt.datatransfer;
+
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * Provides an abstract file that can be transfered
+ * to the drop target without first existing on the local filesystem.
+ *
+ * @author      Damjan Jovanovic
+ */
+
+public abstract class AbstractFile {
+
+    /**
+     * Gets the relative path and filename the file will have
+     * on the filesystem when it is written.
+     *
+     * @return the relative path and filename the file will have on the filesystem when it is written.
+     */
+    public abstract File getPath();
+
+    /**
+     * Opens the file so its contents can be written out
+     * to the filesystem.
+     *
+     * @return the <code>InputStream</code> with the file's contents.
+     */
+    public abstract InputStream open();
+}
diff -r ca34cfff70a4 src/share/classes/java/awt/datatransfer/DataFlavor.java
--- a/src/share/classes/java/awt/datatransfer/DataFlavor.java	Fri Nov 27 16:07:32 2009 +0300
+++ b/src/share/classes/java/awt/datatransfer/DataFlavor.java	Mon Dec 07 20:05:03 2009 +0200
@@ -221,6 +221,23 @@
     public static final DataFlavor javaFileListFlavor = createConstant("application/x-java-file-list;class=java.util.List", null);
 
     /**
+     * To transfer the <code>URL</code> where files should be written
+     * from the transfer target back to the source, a <code>DataFlavor</code>
+     * of this type/subtype and representation class of
+     * <code>java.awt.datatransfer.LocationReferral</code> is used.
+     */
+    public static final DataFlavor locationReferralFlavor = createConstant("application/x-java-location-referral;class=java.awt.datatransfer.LocationReferral", null);
+
+    /**
+     * To transfer files which don't exist on the local filesystem and/or
+     * are dynamically generated during the transfer, a <code>DataFlavor</code>
+     * of this type/subtype and representantion class of <code>java.util.List</code>
+     * is used. Each element of this list is required to be of type
+     * <code>java.awt.datatransfer.AbstractFile</code>.
+     */
+    public static final DataFlavor abstractFilesFlavor = createConstant("application/x-java-abstract-files;class=java.util.List", null);
+
+    /**
      * To transfer a reference to an arbitrary Java object reference that
      * has no associated MIME Content-type, across a <code>Transferable</code>
      * interface WITHIN THE SAME JVM, a <code>DataFlavor</code>
@@ -1228,6 +1245,32 @@
    }
 
     /**
+     * Returns true if the <code>DataFlavor</code> specified represents
+     * a LocationReferral object.
+     * @return true if the <code>DataFlavor</code> specified represents
+     *   a LocationReferral object.
+     */
+    public boolean isFlavorLocationReferralType() {
+        if (mimeType == null || representationClass == null)
+            return false;
+        return LocationReferral.class.isAssignableFrom(representationClass) &&
+            mimeType.match(locationReferralFlavor.mimeType);
+    }
+
+    /**
+     * Returns true if the <code>DataFlavor</code> specified represents
+     * a list of AbstractFile objects.
+     * @return true if the <code>DataFlavor</code> specified represents
+     *   a list of AbstractFile objects.
+     */
+    public boolean isFlavorAbstractFilesType() {
+        if (mimeType == null || representationClass == null)
+            return false;
+        return java.util.List.class.isAssignableFrom(representationClass) &&
+            mimeType.match(abstractFilesFlavor.mimeType);
+    }
+
+    /**
      * Returns whether this <code>DataFlavor</code> is a valid text flavor for
      * this implementation of the Java platform. Only flavors equivalent to
      * <code>DataFlavor.stringFlavor</code> and <code>DataFlavor</code>s with
diff -r ca34cfff70a4 src/share/classes/java/awt/datatransfer/LocationReferral.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/java/awt/datatransfer/LocationReferral.java	Mon Dec 07 20:05:03 2009 +0200
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2009 Damjan Jovanovic and Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package java.awt.datatransfer;
+
+import java.net.URL;
+
+/**
+ * Provides a way for the data transfer target to notify the
+ * transfer source about the location it represents, so the
+ * transfer source can write files there.
+ *
+ * @author      Damjan Jovanovic
+ */
+
+public interface LocationReferral {
+
+    /**
+     * Sets the location where the transfer source will attempt to
+     * write files. Returns when the files have been written.
+     *
+     * @param url the URL where files will be written
+     * @return whether the transfer succeeded
+     */
+    public boolean setLocation(URL url);
+
+}
diff -r ca34cfff70a4 src/share/classes/sun/awt/datatransfer/DataTransferer.java
--- a/src/share/classes/sun/awt/datatransfer/DataTransferer.java	Fri Nov 27 16:07:32 2009 +0300
+++ b/src/share/classes/sun/awt/datatransfer/DataTransferer.java	Mon Dec 07 20:05:03 2009 +0200
@@ -637,6 +637,22 @@
     }
 
     /**
+     * Determines whether the format is a location referral we can convert to
+     * a DataFlavor.locationReferralFlavor.
+     */
+    protected boolean isLocationReferralFormat(long format) {
+        return false;
+    }
+
+    /**
+     * Determines whether the format is an abstract file list we can convert to
+     * a list of AbstractFile.
+     */
+    protected boolean isAbstractFilesFormat(long format) {
+        return false;
+    }
+
+    /**
      * Returns a Map whose keys are all of the possible formats into which the
      * Transferable's transfer data flavors can be translated. The value of
      * each key is the DataFlavor in which the Transferable's data should be
@@ -704,6 +720,8 @@
             // case of Serializable
             if (flavor.isFlavorTextType() ||
                 flavor.isFlavorJavaFileListType() ||
+                flavor.isFlavorLocationReferralType() ||
+                flavor.isFlavorAbstractFilesType() ||
                 DataFlavor.imageFlavor.equals(flavor) ||
                 flavor.isRepresentationClassSerializable() ||
                 flavor.isRepresentationClassInputStream() ||
@@ -807,6 +825,8 @@
                 // case of Serializable
                 if (flavor.isFlavorTextType() ||
                     flavor.isFlavorJavaFileListType() ||
+                    flavor.isFlavorLocationReferralType() ||
+                    flavor.isFlavorAbstractFilesType() ||
                     DataFlavor.imageFlavor.equals(flavor) ||
                     flavor.isRepresentationClassSerializable() ||
                     flavor.isRepresentationClassInputStream() ||
@@ -883,6 +903,8 @@
                 // case of Serializable
                 if (flavor.isFlavorTextType() ||
                     flavor.isFlavorJavaFileListType() ||
+                    flavor.isFlavorLocationReferralType() ||
+                    flavor.isFlavorAbstractFilesType() ||
                     DataFlavor.imageFlavor.equals(flavor) ||
                     flavor.isRepresentationClassSerializable() ||
                     flavor.isRepresentationClassInputStream() ||
@@ -1357,6 +1379,15 @@
                 bos.write(eoln, 0, eoln.length);
             }
 
+        // Target data is an AbstractFile list. Source data must be a
+        // java.util.List containing java.awt.datatransfer.AbstractFile instances.
+        } else if (isAbstractFilesFormat(format)) {
+            if (!flavor.isFlavorAbstractFilesType()) {
+                throw new IOException("data translation failed");
+            }
+            List abstractFiles = (List)obj;
+            return transferAbstractFiles(abstractFiles);
+
         // Source data is an InputStream. For arbitrary flavors, just grab the
         // bytes and dump them into a byte array. For text flavors, decode back
         // to a String and recur to reencode according to the requested format.
@@ -1406,6 +1437,10 @@
 
     protected abstract ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException;
 
+    protected byte[] transferAbstractFiles(List abstractFiles) throws IOException {
+        throw new IOException(new UnsupportedOperationException());
+    }
+
     private String removeSuspectedData(DataFlavor flavor, final Transferable contents, final String str)
             throws IOException
     {
diff -r ca34cfff70a4 src/share/classes/sun/awt/dnd/SunDropTargetContextPeer.java
--- a/src/share/classes/sun/awt/dnd/SunDropTargetContextPeer.java	Fri Nov 27 16:07:32 2009 +0300
+++ b/src/share/classes/sun/awt/dnd/SunDropTargetContextPeer.java	Mon Dec 07 20:05:03 2009 +0200
@@ -29,6 +29,7 @@
 import java.awt.Point;
 
 import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.LocationReferral;
 import java.awt.datatransfer.Transferable;
 import java.awt.datatransfer.UnsupportedFlavorException;
 
@@ -258,6 +259,8 @@
             } catch (IOException e) {
                 throw new InvalidDnDOperationException(e.getMessage());
             }
+        } else if (ret instanceof LocationReferral) {
+            return ret;
         } else {
             throw new IOException("no native data was transfered");
         }
diff -r ca34cfff70a4 src/solaris/classes/sun/awt/X11/XDataTransferer.java
--- a/src/solaris/classes/sun/awt/X11/XDataTransferer.java	Fri Nov 27 16:07:32 2009 +0300
+++ b/src/solaris/classes/sun/awt/X11/XDataTransferer.java	Mon Dec 07 20:05:03 2009 +0200
@@ -27,6 +27,7 @@
 
 import java.awt.Image;
 
+import java.awt.datatransfer.AbstractFile;
 import java.awt.datatransfer.DataFlavor;
 import java.awt.datatransfer.Transferable;
 import java.awt.datatransfer.UnsupportedFlavorException;
@@ -36,10 +37,12 @@
 import java.awt.image.WritableRaster;
 
 import java.io.BufferedReader;
+import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.IOException;
 
+import java.net.URL;
 import java.net.URI;
 import java.net.URISyntaxException;
 
@@ -68,6 +71,7 @@
     static final XAtom TARGETS_ATOM = XAtom.get("TARGETS");
     static final XAtom INCR_ATOM = XAtom.get("INCR");
     static final XAtom MULTIPLE_ATOM = XAtom.get("MULTIPLE");
+    static final XAtom XdndDirectSave0_ATOM = XAtom.get("XdndDirectSave0");
 
     /**
      * Singleton constructor
@@ -133,6 +137,14 @@
         return false;
     }
 
+    protected boolean isLocationReferralFormat(long format) {
+        return format == XdndDirectSave0_ATOM.getAtom();
+    }
+
+    protected boolean isAbstractFilesFormat(long format) {
+        return format == XdndDirectSave0_ATOM.getAtom();
+    }
+
     public boolean isFileFormat(long format) {
         return format == FILE_NAME_ATOM.getAtom() ||
             format == DT_NET_FILE_ATOM.getAtom();
@@ -208,6 +220,63 @@
         return bos;
     }
 
+    protected byte[] transferAbstractFiles(List abstractFiles) throws IOException
+    {
+        final long window = XDragSourceProtocol.getDragSourceWindow();
+        WindowPropertyGetter wpg = new WindowPropertyGetter(
+            window,
+            XDataTransferer.XdndDirectSave0_ATOM,
+            0, 0xFFFF, false,
+            XConstants.AnyPropertyType);
+        String path;
+        try {
+            int status = wpg.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());
+            if (status != XConstants.Success) {
+                throw new IOException("no window");
+            }
+            String urlString = new String(Native.toBytes(wpg.getData(), wpg.getNumberOfItems()));
+            if (urlString.equals("xds.txt")) {
+                throw new IOException("XdndDirectSave0 window property unchanged by drop target");
+            }
+            URL url = new URL(urlString);
+            if (!url.getProtocol().equals("file")) {
+                throw new IOException("Non-file URLs are not supported on this platform");
+            }
+            path = url.getPath();
+            if (!path.endsWith("/xds.txt")) {
+                throw new IOException("XdndDirectSave0 window property badly changed by drop target");
+            }
+            path = path.substring(0, path.length() - 7);
+        } finally {
+            wpg.dispose();
+        }
+
+        for (Object object : abstractFiles) {
+            if (object instanceof AbstractFile) {
+                AbstractFile abstractFile = (AbstractFile) object;
+                InputStream inputStream = null;
+                FileOutputStream fileOutputStream = null;
+                try {
+                    inputStream = abstractFile.open();
+                    fileOutputStream = new FileOutputStream(path + abstractFile.getPath().toString());
+                    byte[] buffer = new byte[64 * 1024];
+                    int bytesRead;
+                    while ((bytesRead = inputStream.read(buffer)) >= 0) {
+                        fileOutputStream.write(buffer, 0, bytesRead);
+                    }
+                } finally {
+                    if (inputStream != null) {
+                        inputStream.close();
+                    }
+                    if (fileOutputStream != null) {
+                        fileOutputStream.close();
+                    }
+                }
+            }
+        }
+        return new byte[] {(byte)'S'};
+    }
+
     /**
      * Translates either a byte array or an input stream which contain
      * platform-specific image data in the given format into an Image.
diff -r ca34cfff70a4 src/solaris/classes/sun/awt/X11/XDnDDragSourceProtocol.java
--- a/src/solaris/classes/sun/awt/X11/XDnDDragSourceProtocol.java	Fri Nov 27 16:07:32 2009 +0300
+++ b/src/solaris/classes/sun/awt/X11/XDnDDragSourceProtocol.java	Mon Dec 07 20:05:03 2009 +0200
@@ -133,6 +133,23 @@
             data = 0;
         }
 
+        for (long format : formats) {
+            if (format == XDataTransferer.XdndDirectSave0_ATOM.getAtom()) {
+                byte[] xds_txt = "xds.txt".getBytes();
+                long directSaveData = Native.toData(xds_txt);
+                try {
+                    XToolkit.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
+                    XDataTransferer.XdndDirectSave0_ATOM.setAtomData8(window,
+                                                                      XAtom.get("text/plain").getAtom(),
+                                                                      directSaveData, xds_txt.length);
+                    XToolkit.RESTORE_XERROR_HANDLER();
+                } finally {
+                    unsafe.freeMemory(directSaveData);
+                }
+                break;
+            }
+        }
+
         if (!XDnDConstants.XDnDSelection.setOwner(contents, formatMap, formats,
                                                   XConstants.CurrentTime)) {
             cleanup();
diff -r ca34cfff70a4 src/solaris/classes/sun/awt/X11/XDnDDropTargetProtocol.java
--- a/src/solaris/classes/sun/awt/X11/XDnDDropTargetProtocol.java	Fri Nov 27 16:07:32 2009 +0300
+++ b/src/solaris/classes/sun/awt/X11/XDnDDropTargetProtocol.java	Mon Dec 07 20:05:03 2009 +0200
@@ -27,12 +27,18 @@
 
 import java.awt.Point;
 
+import java.awt.datatransfer.LocationReferral;
+
 import java.awt.dnd.DnDConstants;
 
+import java.awt.dnd.InvalidDnDOperationException;
+
 import java.awt.event.MouseEvent;
 
 import java.io.IOException;
 
+import java.net.URL;
+
 import sun.util.logging.PlatformLogger;
 
 import sun.misc.Unsafe;
@@ -58,6 +64,7 @@
     private int sourceX = 0;
     private int sourceY = 0;
     private XWindow targetXWindow = null;
+    private XDropDirectSaver xdropDirectSaver = null;
 
     // XEmbed stuff.
     private long prevCtxt = 0;
@@ -838,6 +845,84 @@
         return true;
     }
 
+    // See http://www.freedesktop.org/wiki/Specifications/direct-save
+    private class XDropDirectSaver implements LocationReferral {
+        private long time_stamp;
+        private boolean wasUsed;
+
+        public XDropDirectSaver(long time_stamp) {
+            this.time_stamp = time_stamp;
+        }
+
+        public boolean setLocation(URL location) {
+            if (wasUsed) {
+                throw new InvalidDnDOperationException("A LocationReferral can only be used once");
+            }
+
+            int status;
+            WindowPropertyGetter wpg = new WindowPropertyGetter(
+                getSourceWindow(),
+                XDataTransferer.XdndDirectSave0_ATOM,
+                0, 0xFFFF, false,
+                XConstants.AnyPropertyType);
+            try {
+                status = wpg.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());
+                if (status != XConstants.Success) {
+                    System.out.println("wpg failed");
+                    return false;
+                }
+                String filename = new String(Native.toBytes(wpg.getData(), wpg.getNumberOfItems()));
+                String path = location.getPath();
+                if (!path.endsWith("/")) {
+                    path = path + "/";
+                }
+                path += filename;
+                if (location.getProtocol().equals("file")) {
+                    // File roller doesn't save the file with file:/path/to/file,
+                    // it needs file:///path/to/file
+                    path = "//" + path;
+                }
+                String result;
+                try {
+                    result = new URL(location.getProtocol(), location.getHost(), location.getPort(), path).toString();
+                } catch (Exception exception) {
+                    return false;
+                }
+                byte[] resultBytes = result.getBytes();
+
+                long data = Native.toData(resultBytes);
+                try {
+                    XToolkit.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
+                    XDataTransferer.XdndDirectSave0_ATOM.setAtomData8(
+                        getSourceWindow(), wpg.getActualType(), data, resultBytes.length);
+                    XToolkit.RESTORE_XERROR_HANDLER();
+
+                    if (XToolkit.saved_error != null &&
+                        XToolkit.saved_error.get_error_code() != XConstants.Success) {
+                        System.out.println("Setting property failed");
+                        return false;
+                    }
+                } finally {
+                    wasUsed = true;
+                    unsafe.freeMemory(data);
+                }
+
+                // This will actually request the file(s) be written by the drag source
+                try {
+                    byte[] response = (byte[]) XDnDConstants.XDnDSelection.getData(
+                        XDataTransferer.XdndDirectSave0_ATOM.getAtom(), time_stamp);
+                    System.out.println("Got " + response.length + " bytes of XdndDirectSave0");
+                    return response.length == 1 && response[0] == 'S';
+                } catch (IOException ioException) {
+                    System.out.println("IO Exception");
+                    return false;
+                }
+            } finally {
+                wpg.dispose();
+            }
+        }
+    }
+
     public Object getData(long ctxt, long format)
       throws IllegalArgumentException, IOException {
         XClientMessageEvent xclient = new XClientMessageEvent(ctxt);
@@ -856,7 +941,18 @@
             throw new IllegalArgumentException();
         }
 
-        return XDnDConstants.XDnDSelection.getData(format, time_stamp);
+        if (format == XDataTransferer.XdndDirectSave0_ATOM.getAtom()) {
+            if (message_type != XDnDConstants.XA_XdndDrop.getAtom()) {
+                throw new InvalidDnDOperationException(
+                    "LocationReferral flavor cannot be requested outside a drop");
+            }
+            if (xdropDirectSaver == null) {
+                xdropDirectSaver = new XDropDirectSaver(time_stamp);
+            }
+            return xdropDirectSaver;
+        } else {
+            return XDnDConstants.XDnDSelection.getData(format, time_stamp);
+        }
     }
 
     public boolean sendDropDone(long ctxt, boolean success, int dropAction) {
@@ -982,6 +1078,7 @@
         sourceX = 0;
         sourceY = 0;
         targetXWindow = null;
+        xdropDirectSaver = null;
     }
 
     public boolean isDragOverComponent() {
diff -r ca34cfff70a4 src/solaris/lib/flavormap.properties
--- a/src/solaris/lib/flavormap.properties	Fri Nov 27 16:07:32 2009 +0300
+++ b/src/solaris/lib/flavormap.properties	Mon Dec 07 20:05:03 2009 +0200
@@ -76,3 +76,5 @@
 text/uri-list=application/x-java-file-list;class=java.util.List
 PNG=image/x-java-image;class=java.awt.Image
 JFIF=image/x-java-image;class=java.awt.Image
+XdndDirectSave0=application/x-java-location-referral;class=java.awt.datatransfer.LocationReferral
+XdndDirectSave0=application/x-java-abstract-files;class=java.util.List
import javax.swing.*;
import java.awt.datatransfer.*;
import java.io.*;
import java.util.*;

public class Drop extends JFrame {
	public static void main(String[] args) throws Exception {
		final Drop drop = new Drop();
		drop.setTitle("Drop files here");
		drop.setTransferHandler(new TransferHandler() {
			@Override
			public boolean canImport(TransferHandler.TransferSupport transferSupport) {
				try {
					Transferable transferable = transferSupport.getTransferable();
					System.out.println("FLAVORS:");
					for (DataFlavor dataFlavor : transferable.getTransferDataFlavors()) {
						System.out.println(dataFlavor);
					}
					System.out.println();
				} catch (Exception ex) {
					ex.printStackTrace();
				}

				return transferSupport.isDataFlavorSupported(DataFlavor.locationReferralFlavor);
			}

			@Override
			public boolean importData(TransferHandler.TransferSupport transferSupport) {
				if (transferSupport.isDataFlavorSupported(DataFlavor.locationReferralFlavor)) {
					Transferable transferable = transferSupport.getTransferable();
					try {
						LocationReferral locationReferral = (LocationReferral) transferable.getTransferData(DataFlavor.locationReferralFlavor);
						System.out.println("Got location referral");
						transferSupport.setDropAction(COPY);
						System.out.println("setLocation() = " + locationReferral.setLocation(new java.net.URL("file:///tmp")));
						return true;
					} catch (Exception ex) {
						ex.printStackTrace();
						return false;
					}
				}
				return false;
			}
		});
		drop.setVisible(true);
	}
}

Reply via email to