/* code from: http://iharder.sourceforge.net/current/java/filedrop/ (public domain) with only very small additions */ package org.openstreetmap.josm.gui; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.dnd.InvalidDnDOperationException; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.Reader; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.TooManyListenersException; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.border.Border; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.OpenFileAction; /** * This class makes it easy to drag and drop files from the operating * system to a Java program. Any {@link java.awt.Component} can be * dropped onto, but only {@link javax.swing.JComponent}s will indicate * the drop event with a changed border. *
* To use this class, construct a new FileDrop by passing * it the target component and a Listener to receive notification * when file(s) have been dropped. Here is an example: *
*
* JPanel myPanel = new JPanel();
* new FileDrop( myPanel, new FileDrop.Listener()
* { public void filesDropped( java.io.File[] files )
* {
* // handle file drop
* ...
* } // end filesDropped
* }); // end FileDrop.Listener
*
*
* You can specify the border that will appear when files are being dragged by * calling the constructor with a {@link javax.swing.border.Border}. Only * JComponents will show any indication with a border. *
* You can turn on some debugging features by passing a PrintStream * object (such as System.out) into the full constructor. A null * value will result in no extra debugging information being output. *
* *
I'm releasing this code into the Public Domain. Enjoy. *
*Original author: Robert Harder, rharder@usa.net
*2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added.
* * @author Robert Harder * @author rharder@users.sf.net * @version 1.0.1 * @since 1231 */ public class FileDrop { private transient Border normalBorder; private transient DropTargetListener dropListener; /** Discover if the running JVM is modern enough to have drag and drop. */ private static Boolean supportsDnD; // Default border color private static Color defaultBorderColor = new Color( 0f, 0f, 1f, 0.25f ); /** * Constructor for JOSM file drop * @param c The drop target */ public FileDrop(final Component c){ this( c, // Drop target BorderFactory.createMatteBorder( 2, 2, 2, 2, defaultBorderColor ), // Drag border true, // Recursive new FileDrop.Listener(){ @Override public void filesDropped( File[] files ){ // start asynchronous loading of files OpenFileAction.OpenFileTask task = new OpenFileAction.OpenFileTask(Arrays.asList(files), null); task.setRecordHistory(true); Main.worker.submit(task); } } ); } /** * Full constructor with a specified border and debugging optionally turned on. * With Debugging turned on, more status messages will be displayed to * out. A common way to use this constructor is with * System.out or System.err. A null value for * the parameter out will result in no debugging output. * * @param c Component on which files will be dropped. * @param dragBorder Border to use on JComponent when dragging occurs. * @param recursive Recursively set children as drop targets. * @param listener Listens for filesDropped. */ public FileDrop( final Component c, final Border dragBorder, final boolean recursive, final Listener listener) { if( supportsDnD() ) { // Make a drop listener dropListener = new DropTargetListener() { @Override public void dragEnter( DropTargetDragEvent evt ) { Main.trace("FileDrop: dragEnter event." ); // Is this an acceptable drag event? if( isDragOk( evt ) ) { // If it's a Swing component, set its border if( c instanceof JComponent ) { JComponent jc = (JComponent) c; normalBorder = jc.getBorder(); Main.trace("FileDrop: normal border saved." ); jc.setBorder( dragBorder ); Main.trace("FileDrop: drag border set." ); } // end if: JComponent // Acknowledge that it's okay to enter evt.acceptDrag( DnDConstants.ACTION_COPY ); Main.trace("FileDrop: event accepted." ); } // end if: drag ok else { // Reject the drag event evt.rejectDrag(); Main.trace("FileDrop: event rejected." ); } // end else: drag not ok } // end dragEnter @Override public void dragOver( DropTargetDragEvent evt ) { // This is called continually as long as the mouse is // over the drag target. } // end dragOver @Override public void drop( DropTargetDropEvent evt ) { Main.trace("FileDrop: drop event." ); try { // Get whatever was dropped Transferable tr = evt.getTransferable(); // Is it a file list? if (tr.isDataFlavorSupported (DataFlavor.javaFileListFlavor)) { // Say we'll take it. evt.acceptDrop ( DnDConstants.ACTION_COPY ); Main.trace("FileDrop: file list accepted." ); // Get a useful list List> fileList = (List>)tr.getTransferData(DataFlavor.javaFileListFlavor); // Convert list to array final File[] files = fileList.toArray(new File[fileList.size()]); // Alert listener to drop. if( listener != null ) { listener.filesDropped( files ); } // Mark that drop is completed. evt.getDropTargetContext().dropComplete(true); Main.trace("FileDrop: drop complete." ); } // end if: file list else // this section will check for a reader flavor. { // Thanks, Nathan! // BEGIN 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added. DataFlavor[] flavors = tr.getTransferDataFlavors(); boolean handled = false; for (DataFlavor flavor : flavors) { if (flavor.isRepresentationClassReader()) { // Say we'll take it. evt.acceptDrop(DnDConstants.ACTION_COPY); Main.trace("FileDrop: reader accepted."); Reader reader = flavor.getReaderForText(tr); BufferedReader br = new BufferedReader(reader); if (listener != null) { listener.filesDropped(createFileArray(br)); } // Mark that drop is completed. evt.getDropTargetContext().dropComplete(true); Main.trace("FileDrop: drop complete."); handled = true; break; } } if(!handled){ Main.trace("FileDrop: not a file list or reader - abort." ); evt.rejectDrop(); } // END 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added. } // end else: not a file list } // end try catch (IOException | UnsupportedFlavorException e) { Main.warn("FileDrop: "+e.getClass().getSimpleName()+" - abort:" ); Main.error(e); try { evt.rejectDrop(); } catch (InvalidDnDOperationException ex) { // Catch InvalidDnDOperationException to fix #11259 Main.error(ex); } } finally { // If it's a Swing component, reset its border if( c instanceof JComponent ) { JComponent jc = (JComponent) c; jc.setBorder( normalBorder ); Main.debug("FileDrop: normal border restored." ); } // end if: JComponent } // end finally } // end drop @Override public void dragExit( DropTargetEvent evt ) { Main.debug("FileDrop: dragExit event." ); // If it's a Swing component, reset its border if( c instanceof JComponent ) { JComponent jc = (JComponent) c; jc.setBorder( normalBorder ); Main.debug("FileDrop: normal border restored." ); } // end if: JComponent } // end dragExit @Override public void dropActionChanged( DropTargetDragEvent evt ) { Main.debug("FileDrop: dropActionChanged event." ); // Is this an acceptable drag event? if( isDragOk( evt ) ) { evt.acceptDrag( DnDConstants.ACTION_COPY ); Main.debug("FileDrop: event accepted." ); } // end if: drag ok else { evt.rejectDrag(); Main.debug("FileDrop: event rejected." ); } // end else: drag not ok } // end dropActionChanged }; // end DropTargetListener // Make the component (and possibly children) drop targets makeDropTarget( c, recursive ); } // end if: supports dnd else { Main.info("FileDrop: Drag and drop is not supported with this JVM" ); } // end else: does not support DnD } // end constructor private static synchronized boolean supportsDnD() { // Static Boolean if( supportsDnD == null ) { boolean support = false; try { Class.forName( "java.awt.dnd.DnDConstants" ); support = true; } catch( Exception e ) { support = false; } supportsDnD = support; } // end if: first time through return supportsDnD.booleanValue(); } // end supportsDnD // BEGIN 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added. private static final String ZERO_CHAR_STRING = "" + (char)0; private static File[] createFileArray(BufferedReader bReader) { try { List
* public class MyClass implements FileDrop.Listener
* ...
* public void filesDropped( java.io.File[] files )
* {
* ...
* } // end filesDropped
* ...
*
*/
public static interface Listener {
/**
* This method is called when files have been successfully dropped.
*
* @param files An array of Files that were dropped.
*/
public abstract void filesDropped( File[] files );
} // end inner-interface Listener
/* ******** I N N E R C L A S S ******** */
/**
* At last an easy way to encapsulate your custom objects for dragging and dropping
* in your Java programs!
* When you need to create a {@link java.awt.datatransfer.Transferable} object,
* use this class to wrap your object.
* For example:
*
* ...
* MyCoolClass myObj = new MyCoolClass();
* Transferable xfer = new TransferableObject( myObj );
* ...
*
* Or if you need to know when the data was actually dropped, like when you're
* moving data out of a list, say, you can use the {@link TransferableObject.Fetcher}
* inner class to return your object Just in Time.
* For example:
*
* ...
* final MyCoolClass myObj = new MyCoolClass();
*
* TransferableObject.Fetcher fetcher = new TransferableObject.Fetcher()
* { public Object getObject(){ return myObj; }
* }; // end fetcher
*
* Transferable xfer = new TransferableObject( fetcher );
* ...
*
*
* The {@link java.awt.datatransfer.DataFlavor} associated with
* {@link TransferableObject} has the representation class
* net.iharder.dnd.TransferableObject.class and MIME type
* application/x-net.iharder.dnd.TransferableObject.
* This data flavor is accessible via the static
* {@link #DATA_FLAVOR} property.
*
*
* I'm releasing this code into the Public Domain. Enjoy.
* * @author Robert Harder * @author rob@iharder.net * @version 1.2 */ public static class TransferableObject implements Transferable { /** * The MIME type for {@link #DATA_FLAVOR} is * application/x-net.iharder.dnd.TransferableObject. */ public static final String MIME_TYPE = "application/x-net.iharder.dnd.TransferableObject"; /** * The default {@link java.awt.datatransfer.DataFlavor} for * {@link TransferableObject} has the representation class * net.iharder.dnd.TransferableObject.class * and the MIME type * application/x-net.iharder.dnd.TransferableObject. */ public static final DataFlavor DATA_FLAVOR = new DataFlavor( FileDrop.TransferableObject.class, MIME_TYPE ); private Fetcher fetcher; private Object data; private DataFlavor customFlavor; /** * Creates a new {@link TransferableObject} that wraps data. * Along with the {@link #DATA_FLAVOR} associated with this class, * this creates a custom data flavor with a representation class * determined fromdata.getClass()
and the MIME type
* application/x-net.iharder.dnd.TransferableObject.
*
* @param data The data to transfer
*/
public TransferableObject( Object data )
{ this.data = data;
this.customFlavor = new DataFlavor( data.getClass(), MIME_TYPE );
} // end constructor
/**
* Creates a new {@link TransferableObject} that will return the
* object that is returned by fetcher.
* No custom data flavor is set other than the default
* {@link #DATA_FLAVOR}.
*
* @see Fetcher
* @param fetcher The {@link Fetcher} that will return the data object
*/
public TransferableObject( Fetcher fetcher )
{ this.fetcher = fetcher;
} // end constructor
/**
* Creates a new {@link TransferableObject} that will return the
* object that is returned by fetcher.
* Along with the {@link #DATA_FLAVOR} associated with this class,
* this creates a custom data flavor with a representation class dataClass
* and the MIME type
* application/x-net.iharder.dnd.TransferableObject.
*
* @see Fetcher
* @param dataClass The {@link java.lang.Class} to use in the custom data flavor
* @param fetcher The {@link Fetcher} that will return the data object
*/
public TransferableObject(Class> dataClass, Fetcher fetcher )
{ this.fetcher = fetcher;
this.customFlavor = new DataFlavor( dataClass, MIME_TYPE );
} // end constructor
/**
* Returns the custom {@link java.awt.datatransfer.DataFlavor} associated
* with the encapsulated object or null if the {@link Fetcher}
* constructor was used without passing a {@link java.lang.Class}.
*
* @return The custom data flavor for the encapsulated object
*/
public DataFlavor getCustomDataFlavor()
{ return customFlavor;
} // end getCustomDataFlavor
/* ******** T R A N S F E R A B L E M E T H O D S ******** */
/**
* Returns a two- or three-element array containing first
* the custom data flavor, if one was created in the constructors,
* second the default {@link #DATA_FLAVOR} associated with
* {@link TransferableObject}, and third the
* {@link java.awt.datatransfer.DataFlavor#stringFlavor}.
*
* @return An array of supported data flavors
*/
@Override
public DataFlavor[] getTransferDataFlavors()
{
if( customFlavor != null )
return new DataFlavor[]
{ customFlavor,
DATA_FLAVOR,
DataFlavor.stringFlavor
}; // end flavors array
else
return new DataFlavor[]
{ DATA_FLAVOR,
DataFlavor.stringFlavor
}; // end flavors array
} // end getTransferDataFlavors
/**
* Returns the data encapsulated in this {@link TransferableObject}.
* If the {@link Fetcher} constructor was used, then this is when
* the {@link Fetcher#getObject getObject()} method will be called.
* If the requested data flavor is not supported, then the
* {@link Fetcher#getObject getObject()} method will not be called.
*
* @param flavor The data flavor for the data to return
* @return The dropped data
*/
@Override
public Object getTransferData( DataFlavor flavor )
throws UnsupportedFlavorException, IOException
{
// Native object
if( flavor.equals( DATA_FLAVOR ) )
return fetcher == null ? data : fetcher.getObject();
// String
if( flavor.equals( DataFlavor.stringFlavor ) )
return fetcher == null ? data.toString() : fetcher.getObject().toString();
// We can't do anything else
throw new UnsupportedFlavorException(flavor);
} // end getTransferData
/**
* Returns true if flavor is one of the supported
* flavors. Flavors are supported using the equals(...)
method.
*
* @param flavor The data flavor to check
* @return Whether or not the flavor is supported
*/
@Override
public boolean isDataFlavorSupported( DataFlavor flavor )
{
// Native object
if( flavor.equals( DATA_FLAVOR ) )
return true;
// String
if( flavor.equals( DataFlavor.stringFlavor ) )
return true;
// We can't do anything else
return false;
} // end isDataFlavorSupported
/* ******** I N N E R I N T E R F A C E F E T C H E R ******** */
/**
* Instead of passing your data directly to the {@link TransferableObject}
* constructor, you may want to know exactly when your data was received
* in case you need to remove it from its source (or do anyting else to it).
* When the {@link #getTransferData getTransferData(...)} method is called
* on the {@link TransferableObject}, the {@link Fetcher}'s
* {@link #getObject getObject()} method will be called.
*
* @author Robert Harder
*/
public static interface Fetcher
{
/**
* Return the object being encapsulated in the
* {@link TransferableObject}.
*
* @return The dropped object
*/
public abstract Object getObject();
} // end inner interface Fetcher
} // end class TransferableObject
} // end class FileDrop