001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.system;
020
021import java.awt.Color;
022import java.awt.Component;
023import java.awt.Container;
024import java.awt.datatransfer.DataFlavor;
025import java.awt.datatransfer.Transferable;
026import java.awt.datatransfer.UnsupportedFlavorException;
027import java.awt.dnd.DnDConstants;
028import java.awt.dnd.DropTarget;
029import java.awt.dnd.DropTargetDragEvent;
030import java.awt.dnd.DropTargetDropEvent;
031import java.awt.dnd.DropTargetEvent;
032import java.awt.dnd.DropTargetListener;
033import java.awt.event.HierarchyEvent;
034import java.awt.event.HierarchyListener;
035import java.io.BufferedReader;
036import java.io.File;
037import java.io.IOException;
038import java.io.PrintStream;
039import java.io.Reader;
040import java.util.ArrayList;
041import java.util.List;
042import java.util.TooManyListenersException;
043
044import javax.swing.BorderFactory;
045import javax.swing.JComponent;
046import javax.swing.border.Border;
047
048/**
049 * This class makes it easy to drag and drop files from the operating
050 * system to a Java program. Any <tt>Component</tt> can be
051 * dropped onto, but only <tt>JComponent</tt>s will indicate
052 * the drop event with a changed
053 * <p/>
054 * To use this class, construct a new <tt>FileDrop</tt> by passing it the target component and a
055 * <tt>Listener</tt> to receive notification when file(s) have been dropped. Here is an example:
056 * <p/>
057 * <code><pre>
058 *      JPanel myPanel = new JPanel();
059 *      new FileDrop( myPanel, new FileDrop.Listener()
060 *      {   public void filesDropped( File[] files )
061 *          {   
062 *              // handle file drop
063 *              ...
064 *          }   // end filesDropped
065 *      }); // end FileDrop.Listener
066 * </pre></code>
067 * <p/>
068 * You can specify the border that will appear when files are being dragged by calling the
069 * constructor with a <tt>Border</tt>. Only <tt>JComponent</tt>s will show any indication with a
070 * <p/>
071 * You can turn on some debugging features by passing a <tt>PrintStream</tt> object (such as
072 * <tt>System.out</tt>) into the full constructor. A <tt>null</tt> value will result in no extra
073 * debugging information being output.
074 * <p/>
075 * <p>
076 * I'm releasing this code into the Public Domain. Enjoy.
077 * </p>
078 * <p>
079 * <em>Original author: Robert Harder, rharder@usa.net</em>
080 * </p>
081 * <p>
082 * 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added.<br>
083 * 2012-04-12 Stephane Dallogneville -- cleanup, modified for ICY
084 * </p>
085 * 
086 * @author Robert Harder
087 * @author rharder@users.sf.net
088 * @author Stephane Dallongeville
089 * @version 1.0.2
090 */
091public class FileDrop
092{
093    public static class TransferableObject implements Transferable
094    {
095        /**
096         * The MIME type for {@link #DATA_FLAVOR} is
097         * <tt>application/x-net.iharder.TransferableObject</tt>.
098         * 
099         * @since 1.1
100         */
101        public final static String MIME_TYPE = "application/x-net.iharder.TransferableObject";
102
103        /**
104         * The default {@link DataFlavor} for {@link TransferableObject} has
105         * the representation class <tt>net.iharder.TransferableObject.class</tt> and the MIME
106         * type <tt>application/x-net.iharder.TransferableObject</tt>.
107         * 
108         * @since 1.1
109         */
110        public final static DataFlavor DATA_FLAVOR = new DataFlavor(TransferableObject.class, MIME_TYPE);
111
112        private Fetcher fetcher;
113        private Object data;
114
115        private DataFlavor customFlavor;
116
117        /**
118         * Creates a new {@link TransferableObject} that wraps <var>data</var>.
119         * Along with the {@link #DATA_FLAVOR} associated with this class,
120         * this creates a custom data flavor with a representation class
121         * determined from <code>data.getClass()</code> and the MIME type
122         * <tt>application/x-net.iharder.TransferableObject</tt>.
123         * 
124         * @param data
125         *        The data to transfer
126         * @since 1.1
127         */
128        public TransferableObject(Object data)
129        {
130            this.data = data;
131            this.customFlavor = new DataFlavor(data.getClass(), MIME_TYPE);
132        } // end constructor
133
134        /**
135         * Creates a new {@link TransferableObject} that will return the
136         * object that is returned by <var>fetcher</var>.
137         * No custom data flavor is set other than the default {@link #DATA_FLAVOR}.
138         * 
139         * @see Fetcher
140         * @param fetcher
141         *        The {@link Fetcher} that will return the data object
142         * @since 1.1
143         */
144        public TransferableObject(Fetcher fetcher)
145        {
146            this.fetcher = fetcher;
147        } // end constructor
148
149        /**
150         * Creates a new {@link TransferableObject} that will return the
151         * object that is returned by <var>fetcher</var>.
152         * Along with the {@link #DATA_FLAVOR} associated with this class,
153         * this creates a custom data flavor with a representation class <var>dataClass</var>
154         * and the MIME type <tt>application/x-net.iharder.TransferableObject</tt>.
155         * 
156         * @see Fetcher
157         * @param dataClass
158         *        The {@link Class} to use in the custom data flavor
159         * @param fetcher
160         *        The {@link Fetcher} that will return the data object
161         * @since 1.1
162         */
163        public TransferableObject(Class dataClass, Fetcher fetcher)
164        {
165            this.fetcher = fetcher;
166            this.customFlavor = new DataFlavor(dataClass, MIME_TYPE);
167        } // end constructor
168
169        /**
170         * Returns the custom {@link DataFlavor} associated
171         * with the encapsulated object or <tt>null</tt> if the {@link Fetcher} constructor was used
172         * without passing a {@link Class}.
173         * 
174         * @return The custom data flavor for the encapsulated object
175         * @since 1.1
176         */
177        public DataFlavor getCustomDataFlavor()
178        {
179            return customFlavor;
180        } // end getCustomDataFlavor
181
182        /* ******** T R A N S F E R A B L E M E T H O D S ******** */
183
184        /**
185         * Returns a two- or three-element array containing first
186         * the custom data flavor, if one was created in the constructors,
187         * second the default {@link #DATA_FLAVOR} associated with {@link TransferableObject}, and
188         * third the {@link DataFlavor#stringFlavor}.
189         * 
190         * @return An array of supported data flavors
191         * @since 1.1
192         */
193        @Override
194        public DataFlavor[] getTransferDataFlavors()
195        {
196            if (customFlavor != null)
197                return new DataFlavor[] {customFlavor, DATA_FLAVOR, DataFlavor.stringFlavor}; // end
198                                                                                              // flavors
199            return new DataFlavor[] {DATA_FLAVOR, DataFlavor.stringFlavor}; // end flavors array
200        } // end getTransferDataFlavors
201
202        /**
203         * Returns the data encapsulated in this {@link TransferableObject}.
204         * If the {@link Fetcher} constructor was used, then this is when
205         * the {@link Fetcher#getObject getObject()} method will be called.
206         * If the requested data flavor is not supported, then the {@link Fetcher#getObject
207         * getObject()} method will not be called.
208         * 
209         * @param flavor
210         *        The data flavor for the data to return
211         * @return The dropped data
212         * @since 1.1
213         */
214        @Override
215        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException
216        {
217            // Native object
218            if (flavor.equals(DATA_FLAVOR))
219                return fetcher == null ? data : fetcher.getObject();
220
221            // String
222            if (flavor.equals(DataFlavor.stringFlavor))
223                return fetcher == null ? data.toString() : fetcher.getObject().toString();
224
225            // We can't do anything else
226            throw new UnsupportedFlavorException(flavor);
227        } // end getTransferData
228
229        /**
230         * Returns <tt>true</tt> if <var>flavor</var> is one of the supported
231         * flavors. Flavors are supported using the <code>equals(...)</code> method.
232         * 
233         * @param flavor
234         *        The data flavor to check
235         * @return Whether or not the flavor is supported
236         * @since 1.1
237         */
238        @Override
239        public boolean isDataFlavorSupported(DataFlavor flavor)
240        {
241            // Native object
242            if (flavor.equals(DATA_FLAVOR))
243                return true;
244
245            // String
246            if (flavor.equals(DataFlavor.stringFlavor))
247                return true;
248
249            // We can't do anything else
250            return false;
251        } // end isDataFlavorSupported
252
253        /* ******** I N N E R - I N T E R F A C E - F E T C H E R ******** */
254
255        /**
256         * Instead of passing your data directly to the {@link TransferableObject} constructor, you
257         * may want to know exactly when your data was received
258         * in case you need to remove it from its source (or do anyting else to it).
259         * When the {@link #getTransferData getTransferData(...)} method is called
260         * on the {@link TransferableObject}, the {@link Fetcher}'s {@link #getObject getObject()}
261         * method will be called.
262         * 
263         * @author Robert Harder
264         * @version 1.1
265         * @since 1.1
266         */
267        public static interface Fetcher
268        {
269            /**
270             * Return the object being encapsulated in the {@link TransferableObject}.
271             * 
272             * @return The dropped object
273             * @since 1.1
274             */
275            public abstract Object getObject();
276        } // end inner interface Fetcher
277
278    } // end class TransferableObject
279
280    transient Border normalBorder;
281    transient DropTargetListener dropListener;
282
283    // Default border color
284    private static Color defaultBorderColor = new Color(0f, 0f, 1f, 0.25f);
285
286    /**
287     * Constructor with a default border and debugging optionally turned on.
288     * With Debugging turned on, more status messages will be displayed to <tt>out</tt>. A common
289     * way to use this constructor is with <tt>System.out</tt> or <tt>System.err</tt>. A
290     * <tt>null</tt> value for
291     * the parameter <tt>out</tt> will result in no debugging output.
292     * 
293     * @param c
294     *        Component on which files will be dropped.
295     * @param listener
296     *        Listens for <tt>filesDropped</tt>.
297     * @since 1.0
298     */
299    public FileDrop(final Component c, final FileDropListener listener)
300    {
301        this(c, BorderFactory.createMatteBorder(2, 2, 2, 2, defaultBorderColor), false, listener);
302    } // end constructor
303
304    /**
305     * Constructor with a default border, debugging optionally turned on
306     * and the option to recursively set drop targets.
307     * If your component is a <tt>Container</tt>, then each of its children
308     * components will also listen for drops, though only the parent will change borders.
309     * With Debugging turned on, more status messages will be displayed to <tt>out</tt>. A common
310     * way to use this constructor is with <tt>System.out</tt> or <tt>System.err</tt>. A
311     * <tt>null</tt> value for
312     * the parameter <tt>out</tt> will result in no debugging output.
313     * 
314     * @param c
315     *        Component on which files will be dropped.
316     * @param recursive
317     *        Recursively set children as drop targets.
318     * @param listener
319     *        Listens for <tt>filesDropped</tt>.
320     * @since 1.0
321     */
322    public FileDrop(final Component c, final boolean recursive, final FileDropListener listener)
323    {
324        this(c, BorderFactory.createMatteBorder(2, 2, 2, 2, defaultBorderColor), recursive, listener);
325    } // end constructor
326
327    /**
328     * Constructor with a specified border
329     * 
330     * @param c
331     *        Component on which files will be dropped.
332     * @param dragBorder
333     *        Border to use on <tt>JComponent</tt> when dragging occurs.
334     * @param listener
335     *        Listens for <tt>filesDropped</tt>.
336     * @since 1.0
337     */
338    public FileDrop(final Component c, final Border dragBorder, final FileDropListener listener)
339    {
340        this(c, dragBorder, false, listener);
341    } // end constructor
342
343    /**
344     * Constructor with a specified border and the option to recursively set drop targets.
345     * If your component is a <tt>Container</tt>, then each of its children
346     * components will also listen for drops, though only the parent will change borders.
347     * 
348     * @param c
349     *        Component on which files will be dropped.
350     * @param dragBorder
351     *        Border to use on <tt>JComponent</tt> when dragging occurs.
352     * @param recursive
353     *        Recursively set children as drop targets.
354     * @param listener
355     *        Listens for <tt>filesDropped</tt>.
356     * @since 1.0
357     */
358    public FileDrop(final Component c, final Border dragBorder, final boolean recursive, final FileDropListener listener)
359    {
360        this(c, dragBorder, recursive, listener, null);
361    } // end constructor
362
363    /**
364     * Constructor with a specified border and the option to recursively set drop targets.
365     * If your component is a <tt>Container</tt>, then each of its children
366     * components will also listen for drops, though only the parent will change borders.
367     * 
368     * @param c
369     *        Component on which files will be dropped.
370     * @param dragBorder
371     *        Border to use on <tt>JComponent</tt> when dragging occurs.
372     * @param recursive
373     *        Recursively set children as drop targets.
374     * @param listener
375     *        Listens for <tt>filesDropped</tt>.
376     * @since 1.0
377     */
378    public FileDrop(final Component c, final Border dragBorder, final boolean recursive,
379            final FileDropExtListener listener)
380    {
381        this(c, dragBorder, recursive, null, listener);
382    }
383
384    /**
385     * Full constructor with a specified border and debugging optionally turned on.
386     * With Debugging turned on, more status messages will be displayed to <tt>out</tt>. A common
387     * way to use this constructor is with <tt>System.out</tt> or <tt>System.err</tt>. A
388     * <tt>null</tt> value for
389     * the parameter <tt>out</tt> will result in no debugging output.
390     * 
391     * @param out
392     *        PrintStream to record debugging info or null for no debugging.
393     * @param c
394     *        Component on which files will be dropped.
395     * @param dragBorder
396     *        Border to use on <tt>JComponent</tt> when dragging occurs.
397     * @param recursive
398     *        Recursively set children as drop targets.
399     * @param listener
400     *        Listens for <tt>filesDropped</tt>.
401     * @since 1.0
402     */
403    FileDrop(final Component c, final Border dragBorder, final boolean recursive, final FileDropListener listener,
404            final FileDropExtListener listenerExt)
405    {
406        // Make a drop listener
407        dropListener = new DropTargetListener()
408        {
409            @Override
410            public void dragEnter(DropTargetDragEvent evt)
411            {
412                // Is this an acceptable drag event?
413                if (isDragOk(evt))
414                {
415                    // If it's a Swing component, set its border
416                    if (c instanceof JComponent)
417                    {
418                        JComponent jc = (JComponent) c;
419                        normalBorder = jc.getBorder();
420                        jc.setBorder(dragBorder);
421                    }
422
423                    // Acknowledge that it's okay to enter
424                    // evt.acceptDrag( DnDConstants.ACTION_COPY_OR_MOVE );
425                    evt.acceptDrag(DnDConstants.ACTION_COPY);
426                }
427                else
428                    // Reject the drag event
429                    evt.rejectDrag();
430            }
431
432            @Override
433            public void dragOver(DropTargetDragEvent evt)
434            { // This is called continually as long as the mouse is
435              // over the drag target.
436            } // end dragOver
437
438            @Override
439            public void drop(DropTargetDropEvent evt)
440            {
441                try
442                { // Get whatever was dropped
443                    Transferable tr = evt.getTransferable();
444
445                    // Is it a file list?
446                    if (tr.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
447                    {
448                        // Say we'll take it.
449                        // evt.acceptDrop ( DnDConstants.ACTION_COPY_OR_MOVE );
450                        evt.acceptDrop(DnDConstants.ACTION_COPY);
451
452                        // Get a useful list
453                        List<File> fileList = (List<File>) tr.getTransferData(DataFlavor.javaFileListFlavor);
454                        // Iterator<File> iterator = fileList.iterator();
455
456                        // Convert list to array
457                        File[] filesTemp = new File[fileList.size()];
458                        fileList.toArray(filesTemp);
459                        final File[] files = filesTemp;
460
461                        // Alert listener to drop.
462                        if (listener != null)
463                            listener.filesDropped(files);
464                        if (listenerExt != null)
465                            listenerExt.filesDropped(evt, files);
466
467                        // Mark that drop is completed.
468                        evt.getDropTargetContext().dropComplete(true);
469                    }
470                    else
471                    // this section will check for a reader flavor.
472                    {
473                        // Thanks, Nathan!
474                        // BEGIN 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added.
475                        DataFlavor[] flavors = tr.getTransferDataFlavors();
476                        boolean handled = false;
477                        for (int zz = 0; zz < flavors.length; zz++)
478                        {
479                            if (flavors[zz].isRepresentationClassReader())
480                            {
481                                // Say we'll take it.
482                                // evt.acceptDrop (
483                                // DnDConstants.ACTION_COPY_OR_MOVE );
484                                evt.acceptDrop(DnDConstants.ACTION_COPY);
485
486                                Reader reader = flavors[zz].getReaderForText(tr);
487
488                                BufferedReader br = new BufferedReader(reader);
489
490                                if (listener != null)
491                                    listener.filesDropped(createFileArray(br));
492                                if (listenerExt != null)
493                                    listenerExt.filesDropped(evt, createFileArray(br));
494
495                                // Mark that drop is completed.
496                                evt.getDropTargetContext().dropComplete(true);
497                                handled = true;
498                                break;
499                            }
500                        }
501
502                        if (!handled)
503                            evt.rejectDrop();
504                        // END 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added.
505                    }
506                }
507                catch (IOException io)
508                {
509                    System.err.println("FileDrop: IOException - abort:");
510                    io.printStackTrace();
511                    evt.rejectDrop();
512                }
513                catch (UnsupportedFlavorException ufe)
514                {
515                    System.err.println("FileDrop: UnsupportedFlavorException - abort:");
516                    ufe.printStackTrace();
517                    evt.rejectDrop();
518                }
519                finally
520                {
521                    // If it's a Swing component, reset its border
522                    if (c instanceof JComponent)
523                    {
524                        JComponent jc = (JComponent) c;
525                        jc.setBorder(normalBorder);
526                    }
527                }
528            }
529
530            @Override
531            public void dragExit(DropTargetEvent evt)
532            {
533                // If it's a Swing component, reset its border
534                if (c instanceof JComponent)
535                {
536                    JComponent jc = (JComponent) c;
537                    jc.setBorder(normalBorder);
538                }
539            }
540
541            @Override
542            public void dropActionChanged(DropTargetDragEvent evt)
543            {
544                // Is this an acceptable drag event?
545                if (isDragOk(evt))
546                    // evt.acceptDrag( DnDConstants.ACTION_COPY_OR_MOVE );
547                    evt.acceptDrag(DnDConstants.ACTION_COPY);
548                else
549                    evt.rejectDrag();
550            }
551        };
552
553        // Make the component (and possibly children) drop targets
554        makeDropTarget(c, recursive);
555    }
556
557    // BEGIN 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added.
558    private static String ZERO_CHAR_STRING = "" + (char) 0;
559
560    static File[] createFileArray(BufferedReader bReader)
561    {
562        try
563        {
564            List<File> list = new ArrayList<File>();
565            String line = null;
566            while ((line = bReader.readLine()) != null)
567            {
568                try
569                {
570                    // kde seems to append a 0 char to the end of the reader
571                    if (ZERO_CHAR_STRING.equals(line))
572                        continue;
573
574                    File file = new File(new java.net.URI(line));
575                    list.add(file);
576                }
577                catch (Exception ex)
578                {
579                    System.err.println("Error with " + line + ": " + ex.getMessage());
580                }
581            }
582
583            return list.toArray(new File[list.size()]);
584        }
585        catch (IOException ex)
586        {
587            System.err.println("FileDrop: IOException");
588        }
589
590        return new File[0];
591    }
592
593    // END 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added.
594
595    private void makeDropTarget(final Component c, boolean recursive)
596    {
597        // Make drop target
598        final DropTarget dt = new DropTarget();
599        try
600        {
601            dt.addDropTargetListener(dropListener);
602        } // end try
603        catch (TooManyListenersException e)
604        {
605            e.printStackTrace();
606            System.err
607                    .println("FileDrop: Drop will not work due to previous error. Do you have another listener attached?");
608        } // end catch
609
610        // Listen for hierarchy changes and remove the drop target when the parent gets cleared out.
611        c.addHierarchyListener(new HierarchyListener()
612        {
613            @Override
614            public void hierarchyChanged(HierarchyEvent evt)
615            {
616                Component parent = c.getParent();
617
618                if (parent == null)
619                    c.setDropTarget(null);
620                else
621                    new DropTarget(c, dropListener);
622            }
623        });
624
625        if (c.getParent() != null)
626            new DropTarget(c, dropListener);
627
628        if (recursive && (c instanceof Container))
629        {
630            // Get the container
631            Container cont = (Container) c;
632            // Get it's components
633            Component[] comps = cont.getComponents();
634
635            // Set it's components as listeners also
636            for (int i = 0; i < comps.length; i++)
637                makeDropTarget(comps[i], recursive);
638        }
639    }
640
641    /** Determine if the dragged data is a file list. */
642    boolean isDragOk(final DropTargetDragEvent evt)
643    {
644        boolean ok = false;
645
646        // Get data flavors being dragged
647        DataFlavor[] flavors = evt.getCurrentDataFlavors();
648
649        // See if any of the flavors are a file list
650        int i = 0;
651        while (!ok && i < flavors.length)
652        {
653            // BEGIN 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added.
654            // Is the flavor a file list?
655            final DataFlavor curFlavor = flavors[i];
656            if (curFlavor.equals(DataFlavor.javaFileListFlavor) || curFlavor.isRepresentationClassReader())
657                ok = true;
658            // END 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added.
659
660            i++;
661        }
662
663        return ok;
664    }
665
666    /**
667     * Removes the drag-and-drop hooks from the component and optionally
668     * from the all children. You should call this if you add and remove
669     * components after you've set up the drag-and-drop.
670     * This will recursively unregister all components contained within
671     * <var>c</var> if <var>c</var> is a {@link Container}.
672     * 
673     * @param c
674     *        The component to unregister as a drop target
675     * @since 1.0
676     */
677    public static boolean remove(Component c)
678    {
679        return remove(null, c, true);
680    }
681
682    /**
683     * Removes the drag-and-drop hooks from the component and optionally
684     * from the all children. You should call this if you add and remove
685     * components after you've set up the drag-and-drop.
686     * 
687     * @param out
688     *        Optional {@link PrintStream} for logging drag and drop messages
689     * @param c
690     *        The component to unregister
691     * @param recursive
692     *        Recursively unregister components within a container
693     * @since 1.0
694     */
695    public static boolean remove(PrintStream out, Component c, boolean recursive)
696    {
697        // Make sure we support
698        c.setDropTarget(null);
699
700        if (recursive && (c instanceof Container))
701        {
702            Component[] comps = ((Container) c).getComponents();
703            for (int i = 0; i < comps.length; i++)
704                remove(out, comps[i], recursive);
705            return true;
706        }
707
708        return false;
709    }
710
711    /* ******** I N N E R - I N T E R F A C E L I S T E N E R ******** */
712
713    /**
714     * Implement this inner interface to listen for when files are dropped. For example
715     * your class declaration may begin like this: <code><pre>
716     *      public class MyClass implements FileDrop.Listener
717     *      ...
718     *      public void filesDropped( File[] files )
719     *      {
720     *          ...
721     *      }   // end filesDropped
722     *      ...
723     * </pre></code>
724     * 
725     * @since 1.1
726     */
727    public static interface FileDropListener
728    {
729
730        /**
731         * This method is called when files have been successfully dropped.
732         * 
733         * @param files
734         *        An array of <tt>File</tt>s that were dropped.
735         * @since 1.0
736         */
737        public abstract void filesDropped(File[] files);
738    }
739
740    /**
741     * Implement this inner interface to listen for when files are dropped. For example
742     * your class declaration may begin like this: <code><pre>
743     *      public class MyClass implements FileDrop.Listener
744     *      ...
745     *      public void filesDropped( File[] files )
746     *      {
747     *          ...
748     *      }   // end filesDropped
749     *      ...
750     * </pre></code>
751     * 
752     * @since 1.1
753     */
754    public static interface FileDropExtListener
755    {
756        /**
757         * This method is called when files have been successfully dropped.
758         * 
759         * @param evt
760         *        The DropTargetDropEvent which initiated the drop operation.
761         * @param files
762         *        An array of <tt>File</tt>s that were dropped.
763         * @since 2.0
764         */
765        public abstract void filesDropped(DropTargetDropEvent evt, File[] files);
766    }
767
768} // end class FileDrop