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