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