001    package org.embl.ebi.escience.scuflui.workbench;
002    
003    import java.awt.Font;
004    
005    /**
006     * This class makes it easy to drag and drop files from the operating system to
007     * a Java program. Any <tt>java.awt.Component</tt> can be dropped onto, but
008     * only <tt>javax.swing.JComponent</tt>s will indicate the drop event with a
009     * changed border. <p/> To use this class, construct a new <tt>FileDrop</tt>
010     * by passing it the target component and a <tt>Listener</tt> to receive
011     * notification when file(s) have been dropped. Here is an example: <p/>
012     * <code><pre>
013     *       JPanel myPanel = new JPanel();
014     *       new FileDrop( myPanel, new FileDrop.Listener()
015     *       {   public void filesDropped( java.io.File[] files )
016     *           {   
017     *               // handle file drop
018     *               ...
019     *           }   // end filesDropped
020     *       }); // end FileDrop.Listener
021     * </pre></code> <p/> You can specify the border that will appear when files are being
022     * dragged by calling the constructor with a <tt>javax.swing.border.Border</tt>.
023     * Only <tt>JComponent</tt>s will show any indication with a border. <p/> You
024     * can turn on some debugging features by passing a <tt>PrintStream</tt>
025     * object (such as <tt>System.out</tt>) into the full constructor. A
026     * <tt>null</tt> value will result in no extra debugging information being
027     * output. <p/>
028     * 
029     * <p>
030     * I'm releasing this code into the Public Domain. Enjoy.
031     * </p>
032     * <p>
033     * <em>Original author: Robert Harder, rharder@usa.net</em>
034     * </p>
035     * 
036     * @author Robert Harder
037     * @author rharder@usa.net
038     * @version 1.0
039     */
040    public class FileDrop {
041            private transient javax.swing.border.Border normalBorder;
042    
043            private transient java.awt.dnd.DropTargetListener dropListener;
044    
045            /** Discover if the running JVM is modern enough to have drag and drop. */
046            private static Boolean supportsDnD;
047    
048            // Default border color
049            private static java.awt.Color defaultBorderColor = new java.awt.Color(0f,
050                            0f, 1f, 0.25f);
051    
052            /**
053             * Constructs a {@link FileDrop} with a default light-blue border and, if
054             * <var>c</var> is a {@link java.awt.Container}, recursively sets all
055             * elements contained within as drop targets, though only the top level
056             * container will change borders.
057             * 
058             * @param c
059             *            Component on which files will be dropped.
060             * @param listener
061             *            Listens for <tt>filesDropped</tt>.
062             * @since 1.0
063             */
064            public FileDrop(final java.awt.Component c, final Listener listener) {
065                    this(null, // Logging stream
066                                    c, // Drop target
067                                    javax.swing.BorderFactory.createMatteBorder(2, 2, 2, 2,
068                                                    defaultBorderColor), // Drag border
069                                    true, // Recursive
070                                    listener);
071            } // end constructor
072    
073            /**
074             * Constructor with a default border and the option to recursively set drop
075             * targets. If your component is a <tt>java.awt.Container</tt>, then each
076             * of its children components will also listen for drops, though only the
077             * parent will change borders.
078             * 
079             * @param c
080             *            Component on which files will be dropped.
081             * @param recursive
082             *            Recursively set children as drop targets.
083             * @param listener
084             *            Listens for <tt>filesDropped</tt>.
085             * @since 1.0
086             */
087            public FileDrop(final java.awt.Component c, final boolean recursive,
088                            final Listener listener) {
089                    this(null, // Logging stream
090                                    c, // Drop target
091                                    javax.swing.BorderFactory.createMatteBorder(2, 2, 2, 2,
092                                                    defaultBorderColor), // Drag border
093                                    recursive, // Recursive
094                                    listener);
095            } // end constructor
096    
097            /**
098             * Constructor with a default border and debugging optionally turned on.
099             * With Debugging turned on, more status messages will be displayed to
100             * <tt>out</tt>. A common way to use this constructor is with
101             * <tt>System.out</tt> or <tt>System.err</tt>. A <tt>null</tt> value
102             * for the parameter <tt>out</tt> will result in no debugging output.
103             * 
104             * @param out
105             *            PrintStream to record debugging info or null for no debugging.
106             * @param c
107             *            Component on which files will be dropped.
108             * @param listener
109             *            Listens for <tt>filesDropped</tt>.
110             * @since 1.0
111             */
112            public FileDrop(final java.io.PrintStream out, final java.awt.Component c,
113                            final Listener listener) {
114                    this(out, // Logging stream
115                                    c, // Drop target
116                                    javax.swing.BorderFactory.createMatteBorder(2, 2, 2, 2,
117                                                    defaultBorderColor), false, // Recursive
118                                    listener);
119            } // end constructor
120    
121            /**
122             * Constructor with a default border, debugging optionally turned on and the
123             * option to recursively set drop targets. If your component is a
124             * <tt>java.awt.Container</tt>, then each of its children components will
125             * also listen for drops, though only the parent will change borders. With
126             * Debugging turned on, more status messages will be displayed to
127             * <tt>out</tt>. A common way to use this constructor is with
128             * <tt>System.out</tt> or <tt>System.err</tt>. A <tt>null</tt> value
129             * for the parameter <tt>out</tt> will result in no debugging output.
130             * 
131             * @param out
132             *            PrintStream to record debugging info or null for no debugging.
133             * @param c
134             *            Component on which files will be dropped.
135             * @param recursive
136             *            Recursively set children as drop targets.
137             * @param listener
138             *            Listens for <tt>filesDropped</tt>.
139             * @since 1.0
140             */
141            public FileDrop(final java.io.PrintStream out, final java.awt.Component c,
142                            final boolean recursive, final Listener listener) {
143                    this(out, // Logging stream
144                                    c, // Drop target
145                                    javax.swing.BorderFactory.createMatteBorder(2, 2, 2, 2,
146                                                    defaultBorderColor), // Drag border
147                                    recursive, // Recursive
148                                    listener);
149            } // end constructor
150    
151            /**
152             * Constructor with a specified border
153             * 
154             * @param c
155             *            Component on which files will be dropped.
156             * @param dragBorder
157             *            Border to use on <tt>JComponent</tt> when dragging occurs.
158             * @param listener
159             *            Listens for <tt>filesDropped</tt>.
160             * @since 1.0
161             */
162            public FileDrop(final java.awt.Component c,
163                            final javax.swing.border.Border dragBorder, final Listener listener) {
164                    this(null, // Logging stream
165                                    c, // Drop target
166                                    dragBorder, // Drag border
167                                    false, // Recursive
168                                    listener);
169            } // end constructor
170    
171            /**
172             * Constructor with a specified border and the option to recursively set
173             * drop targets. If your component is a <tt>java.awt.Container</tt>, then
174             * each of its children components will also listen for drops, though only
175             * the parent will change borders.
176             * 
177             * @param c
178             *            Component on which files will be dropped.
179             * @param dragBorder
180             *            Border to use on <tt>JComponent</tt> when dragging occurs.
181             * @param recursive
182             *            Recursively set children as drop targets.
183             * @param listener
184             *            Listens for <tt>filesDropped</tt>.
185             * @since 1.0
186             */
187            public FileDrop(final java.awt.Component c,
188                            final javax.swing.border.Border dragBorder,
189                            final boolean recursive, final Listener listener) {
190                    this(null, c, dragBorder, recursive, listener);
191            } // end constructor
192    
193            /**
194             * Constructor with a specified border and debugging optionally turned on.
195             * With Debugging turned on, more status messages will be displayed to
196             * <tt>out</tt>. A common way to use this constructor is with
197             * <tt>System.out</tt> or <tt>System.err</tt>. A <tt>null</tt> value
198             * for the parameter <tt>out</tt> will result in no debugging output.
199             * 
200             * @param out
201             *            PrintStream to record debugging info or null for no debugging.
202             * @param c
203             *            Component on which files will be dropped.
204             * @param dragBorder
205             *            Border to use on <tt>JComponent</tt> when dragging occurs.
206             * @param listener
207             *            Listens for <tt>filesDropped</tt>.
208             * @since 1.0
209             */
210            public FileDrop(final java.io.PrintStream out, final java.awt.Component c,
211                            final javax.swing.border.Border dragBorder, final Listener listener) {
212                    this(out, // Logging stream
213                                    c, // Drop target
214                                    dragBorder, // Drag border
215                                    false, // Recursive
216                                    listener);
217            } // end constructor
218    
219            /**
220             * Full constructor with a specified border and debugging optionally turned
221             * on. With Debugging turned on, more status messages will be displayed to
222             * <tt>out</tt>. A common way to use this constructor is with
223             * <tt>System.out</tt> or <tt>System.err</tt>. A <tt>null</tt> value
224             * for the parameter <tt>out</tt> will result in no debugging output.
225             * 
226             * @param out
227             *            PrintStream to record debugging info or null for no debugging.
228             * @param c
229             *            Component on which files will be dropped.
230             * @param dragBorder
231             *            Border to use on <tt>JComponent</tt> when dragging occurs.
232             * @param recursive
233             *            Recursively set children as drop targets.
234             * @param listener
235             *            Listens for <tt>filesDropped</tt>.
236             * @since 1.0
237             */
238            public FileDrop(final java.io.PrintStream out, final java.awt.Component c,
239                            final javax.swing.border.Border dragBorder,
240                            final boolean recursive, final Listener listener) {
241    
242                    if (supportsDnD()) { // Make a drop listener
243                            dropListener = new java.awt.dnd.DropTargetListener() {
244                                    public void dragEnter(java.awt.dnd.DropTargetDragEvent evt) {
245                                            log(out, "FileDrop: dragEnter event.");
246    
247                                            // Is this an acceptable drag event?
248                                            if (isDragOk(out, evt)) {
249                                                    // If it's a Swing component, set its border
250                                                    if (c instanceof javax.swing.JComponent) {
251                                                            javax.swing.JComponent jc = (javax.swing.JComponent) c;
252                                                            normalBorder = jc.getBorder();
253                                                            log(out, "FileDrop: normal border saved.");
254                                                            jc.setBorder(dragBorder);
255                                                            log(out, "FileDrop: drag border set.");
256                                                    } // end if: JComponent
257    
258                                                    // Acknowledge that it's okay to enter
259                                                    // evt.acceptDrag(
260                                                    // java.awt.dnd.DnDConstants.ACTION_COPY_OR_MOVE );
261                                                    evt.acceptDrag(java.awt.dnd.DnDConstants.ACTION_COPY);
262                                                    log(out, "FileDrop: event accepted.");
263                                            } // end if: drag ok
264                                            else { // Reject the drag event
265                                                    evt.rejectDrag();
266                                                    log(out, "FileDrop: event rejected.");
267                                            } // end else: drag not ok
268                                    } // end dragEnter
269    
270                                    public void dragOver(java.awt.dnd.DropTargetDragEvent evt) { // This
271                                                                                                                                                                    // is
272                                                                                                                                                                    // called
273                                                                                                                                                                    // continually
274                                                                                                                                                                    // as
275                                                                                                                                                                    // long
276                                                                                                                                                                    // as
277                                                                                                                                                                    // the
278                                                                                                                                                                    // mouse
279                                                                                                                                                                    // is
280                                            // over the drag target.
281                                    } // end dragOver
282    
283                                    public void drop(java.awt.dnd.DropTargetDropEvent evt) {
284                                            log(out, "FileDrop: drop event.");
285                                            try { // Get whatever was dropped
286                                                    java.awt.datatransfer.Transferable tr = evt
287                                                                    .getTransferable();
288    
289                                                    // Is it a file list?
290                                                    if (tr
291                                                                    .isDataFlavorSupported(java.awt.datatransfer.DataFlavor.javaFileListFlavor)) {
292                                                            // Say we'll take it.
293                                                            // evt.acceptDrop (
294                                                            // java.awt.dnd.DnDConstants.ACTION_COPY_OR_MOVE );
295                                                            evt
296                                                                            .acceptDrop(java.awt.dnd.DnDConstants.ACTION_COPY);
297                                                            log(out, "FileDrop: file list accepted.");
298    
299                                                            // Get a useful list
300                                                            java.util.List fileList = (java.util.List) tr
301                                                                            .getTransferData(java.awt.datatransfer.DataFlavor.javaFileListFlavor);
302                                                            java.util.Iterator iterator = fileList.iterator();
303    
304                                                            // Convert list to array
305                                                            java.io.File[] filesTemp = new java.io.File[fileList
306                                                                            .size()];
307                                                            fileList.toArray(filesTemp);
308                                                            final java.io.File[] files = filesTemp;
309    
310                                                            // Alert listener to drop.
311                                                            if (listener != null)
312                                                                    listener.filesDropped(files);
313    
314                                                            // Mark that drop is completed.
315                                                            evt.getDropTargetContext().dropComplete(true);
316                                                            log(out, "FileDrop: drop complete.");
317                                                    } // end if: file list
318                                                    else {
319                                                            log(out, "FileDrop: not a file list - abort.");
320                                                            evt.rejectDrop();
321                                                    } // end else: not a file list
322                                            } // end try
323                                            catch (java.io.IOException io) {
324                                                    log(out, "FileDrop: IOException - abort:");
325                                                    io.printStackTrace(out);
326                                                    evt.rejectDrop();
327                                            } // end catch IOException
328                                            catch (java.awt.datatransfer.UnsupportedFlavorException ufe) {
329                                                    log(out,
330                                                                    "FileDrop: UnsupportedFlavorException - abort:");
331                                                    ufe.printStackTrace(out);
332                                                    evt.rejectDrop();
333                                            } // end catch: UnsupportedFlavorException
334                                            finally {
335                                                    // If it's a Swing component, reset its border
336                                                    if (c instanceof javax.swing.JComponent) {
337                                                            javax.swing.JComponent jc = (javax.swing.JComponent) c;
338                                                            jc.setBorder(normalBorder);
339                                                            log(out, "FileDrop: normal border restored.");
340                                                    } // end if: JComponent
341                                            } // end finally
342                                    } // end drop
343    
344                                    public void dragExit(java.awt.dnd.DropTargetEvent evt) {
345                                            log(out, "FileDrop: dragExit event.");
346                                            // If it's a Swing component, reset its border
347                                            if (c instanceof javax.swing.JComponent) {
348                                                    javax.swing.JComponent jc = (javax.swing.JComponent) c;
349                                                    jc.setBorder(normalBorder);
350                                                    log(out, "FileDrop: normal border restored.");
351                                            } // end if: JComponent
352                                    } // end dragExit
353    
354                                    public void dropActionChanged(
355                                                    java.awt.dnd.DropTargetDragEvent evt) {
356                                            log(out, "FileDrop: dropActionChanged event.");
357                                            // Is this an acceptable drag event?
358                                            if (isDragOk(out, evt)) { // evt.acceptDrag(
359                                                                                                    // java.awt.dnd.DnDConstants.ACTION_COPY_OR_MOVE
360                                                                                                    // );
361                                                    evt.acceptDrag(java.awt.dnd.DnDConstants.ACTION_COPY);
362                                                    log(out, "FileDrop: event accepted.");
363                                            } // end if: drag ok
364                                            else {
365                                                    evt.rejectDrag();
366                                                    log(out, "FileDrop: event rejected.");
367                                            } // end else: drag not ok
368                                    } // end dropActionChanged
369                            }; // end DropTargetListener
370    
371                            // Make the component (and possibly children) drop targets
372                            makeDropTarget(out, c, recursive);
373                    } // end if: supports dnd
374                    else {
375                            log(out, "FileDrop: Drag and drop is not supported with this JVM");
376                    } // end else: does not support DnD
377            } // end constructor
378    
379            private static boolean supportsDnD() { // Static Boolean
380                    if (supportsDnD == null) {
381                            boolean support = false;
382                            try {
383                                    Class arbitraryDndClass = Class
384                                                    .forName("java.awt.dnd.DnDConstants");
385                                    support = true;
386                            } // end try
387                            catch (Exception e) {
388                                    support = false;
389                            } // end catch
390                            supportsDnD = new Boolean(support);
391                    } // end if: first time through
392                    return supportsDnD.booleanValue();
393            } // end supportsDnD
394    
395            private void makeDropTarget(final java.io.PrintStream out,
396                            final java.awt.Component c, boolean recursive) {
397                    // Make drop target
398                    final java.awt.dnd.DropTarget dt = new java.awt.dnd.DropTarget();
399                    try {
400                            dt.addDropTargetListener(dropListener);
401                    } // end try
402                    catch (java.util.TooManyListenersException e) {
403                            e.printStackTrace();
404                            log(
405                                            out,
406                                            "FileDrop: Drop will not work due to previous error. Do you have another listener attached?");
407                    } // end catch
408    
409                    // Listen for hierarchy changes and remove the drop target when the
410                    // parent gets cleared out.
411                    c.addHierarchyListener(new java.awt.event.HierarchyListener() {
412                            public void hierarchyChanged(java.awt.event.HierarchyEvent evt) {
413                                    log(out, "FileDrop: Hierarchy changed.");
414                                    java.awt.Component parent = c.getParent();
415                                    if (parent == null) {
416                                            c.setDropTarget(null);
417                                            log(out, "FileDrop: Drop target cleared from component.");
418                                    } // end if: null parent
419                                    else {
420                                            new java.awt.dnd.DropTarget(c, dropListener);
421                                            log(out, "FileDrop: Drop target added to component.");
422                                    } // end else: parent not null
423                            } // end hierarchyChanged
424                    }); // end hierarchy listener
425                    if (c.getParent() != null)
426                            new java.awt.dnd.DropTarget(c, dropListener);
427    
428                    if (recursive && (c instanceof java.awt.Container)) {
429                            // Get the container
430                            java.awt.Container cont = (java.awt.Container) c;
431    
432                            // Get it's components
433                            java.awt.Component[] comps = cont.getComponents();
434    
435                            // Set it's components as listeners also
436                            for (int i = 0; i < comps.length; i++)
437                                    makeDropTarget(out, comps[i], recursive);
438                    } // end if: recursively set components as listener
439            } // end dropListener
440    
441            /** Determine if the dragged data is a file list. */
442            private boolean isDragOk(final java.io.PrintStream out,
443                            final java.awt.dnd.DropTargetDragEvent evt) {
444                    boolean ok = false;
445    
446                    // Get data flavors being dragged
447                    java.awt.datatransfer.DataFlavor[] flavors = evt
448                                    .getCurrentDataFlavors();
449    
450                    // See if any of the flavors are a file list
451                    int i = 0;
452                    while (!ok && i < flavors.length) { // Is the flavor a file list?
453                            if (flavors[i]
454                                            .equals(java.awt.datatransfer.DataFlavor.javaFileListFlavor))
455                                    ok = true;
456                            i++;
457                    } // end while: through flavors
458    
459                    // If logging is enabled, show data flavors
460                    if (out != null) {
461                            if (flavors.length == 0)
462                                    log(out, "FileDrop: no data flavors.");
463                            for (i = 0; i < flavors.length; i++)
464                                    log(out, flavors[i].toString());
465                    } // end if: logging enabled
466    
467                    return ok;
468            } // end isDragOk
469    
470            /** Outputs <tt>message</tt> to <tt>out</tt> if it's not null. */
471            private static void log(java.io.PrintStream out, String message) { // Log
472                                                                                                                                                    // message
473                                                                                                                                                    // if
474                                                                                                                                                    // requested
475                    if (out != null)
476                            out.println(message);
477            } // end log
478    
479            /**
480             * Removes the drag-and-drop hooks from the component and optionally from
481             * the all children. You should call this if you add and remove components
482             * after you've set up the drag-and-drop. This will recursively unregister
483             * all components contained within <var>c</var> if <var>c</var> is a
484             * {@link java.awt.Container}.
485             * 
486             * @param c
487             *            The component to unregister as a drop target
488             * @since 1.0
489             */
490            public static boolean remove(java.awt.Component c) {
491                    return remove(null, c, true);
492            } // end remove
493    
494            /**
495             * Removes the drag-and-drop hooks from the component and optionally from
496             * the all children. You should call this if you add and remove components
497             * after you've set up the drag-and-drop.
498             * 
499             * @param out
500             *            Optional {@link java.io.PrintStream} for logging drag and drop
501             *            messages
502             * @param c
503             *            The component to unregister
504             * @param recursive
505             *            Recursively unregister components within a container
506             * @since 1.0
507             */
508            public static boolean remove(java.io.PrintStream out, java.awt.Component c,
509                            boolean recursive) { // Make sure we support dnd.
510                    if (supportsDnD()) {
511                            log(out, "FileDrop: Removing drag-and-drop hooks.");
512                            c.setDropTarget(null);
513                            if (recursive && (c instanceof java.awt.Container)) {
514                                    java.awt.Component[] comps = ((java.awt.Container) c)
515                                                    .getComponents();
516                                    for (int i = 0; i < comps.length; i++)
517                                            remove(out, comps[i], recursive);
518                                    return true;
519                            } // end if: recursive
520                            else
521                                    return false;
522                    } // end if: supports DnD
523                    else
524                            return false;
525            } // end remove
526    
527            /** Runs a sample program that shows dropped files */
528            public static void main(String[] args) {
529                    javax.swing.JFrame frame = new javax.swing.JFrame("FileDrop");
530                    // javax.swing.border.TitledBorder dragBorder = new
531                    // javax.swing.border.TitledBorder( "Drop 'em" );
532                    final javax.swing.JTextArea text = new javax.swing.JTextArea();
533                    text.setFont(Font.getFont("Monospaced"));
534                    frame.getContentPane().add(new javax.swing.JScrollPane(text),
535                                    java.awt.BorderLayout.CENTER);
536    
537                    new FileDrop(System.out, text, /* dragBorder, */new FileDrop.Listener() {
538                            public void filesDropped(java.io.File[] files) {
539                                    for (int i = 0; i < files.length; i++) {
540                                            try {
541                                                    text.append(files[i].getCanonicalPath() + "\n");
542                                            } // end try
543                                            catch (java.io.IOException e) {
544                                            }
545                                    } // end for: through each dropped file
546                            } // end filesDropped
547                    }); // end FileDrop.Listener
548    
549                    frame.setBounds(100, 100, 300, 400);
550                    frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
551                    // frame.show();
552                    frame.setVisible(true);
553            } // end main
554    
555            /* ******** I N N E R I N T E R F A C E L I S T E N E R ******** */
556    
557            /**
558             * Implement this inner interface to listen for when files are dropped. For
559             * example your class declaration may begin like this: <code><pre>
560             *       public class MyClass implements FileDrop.Listener
561             *       ...
562             *       public void filesDropped( java.io.File[] files )
563             *       {
564             *           ...
565             *       }   // end filesDropped
566             *       ...
567             * </pre></code>
568             * 
569             * @since 1.0
570             */
571            public interface Listener {
572                    /**
573                     * This method is called when files have been successfully dropped.
574                     * 
575                     * @param files
576                     *            An array of <tt>File</tt>s that were dropped.
577                     * @since 1.0
578                     */
579                    public abstract void filesDropped(java.io.File[] files);
580            } // end inner-interface Listener
581    
582    } // end class FileDrop