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