View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2002  Oliver Burn
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.gui;
21  
22  import java.awt.Color;
23  import java.awt.Component;
24  import java.awt.Container;
25  import java.awt.datatransfer.DataFlavor;
26  import java.awt.datatransfer.Transferable;
27  import java.awt.datatransfer.UnsupportedFlavorException;
28  import java.awt.dnd.DnDConstants;
29  import java.awt.dnd.DropTarget;
30  import java.awt.dnd.DropTargetDragEvent;
31  import java.awt.dnd.DropTargetDropEvent;
32  import java.awt.dnd.DropTargetEvent;
33  import java.awt.dnd.DropTargetListener;
34  import java.awt.event.HierarchyEvent;
35  import java.awt.event.HierarchyListener;
36  import java.io.File;
37  import java.io.IOException;
38  import java.util.List;
39  import java.util.TooManyListenersException;
40  import javax.swing.BorderFactory;
41  import javax.swing.JComponent;
42  import javax.swing.border.Border;
43  
44  /**
45   * This class makes it easy to drag and drop files from the operating
46   * system to a Java program. Any <tt>java.awt.Component</tt> can be
47   * dropped onto, but only <tt>javax.swing.JComponent</tt>s will indicate
48   * the drop event with a changed border.
49   * <p/>
50   * To use this class, construct a new <tt>FileDrop</tt> by passing
51   * it the target component and a <tt>Listener</tt> to receive notification
52   * when file(s) have been dropped. Here is an example:
53   * <p/>
54   * <code><pre>
55   *      JPanel myPanel = new JPanel();
56   *      new FileDrop( myPanel, new FileDrop.Listener()
57   *      {   public void filesDropped( java.io.File[] files )
58   *          {
59   *              // handle file drop
60   *              ...
61   *          }   // end filesDropped
62   *      }); // end FileDrop.Listener
63   * </pre></code>
64   * <p/>
65   * You can specify the border that will appear when files are being dragged by
66   * calling the constructor with a <tt>javax.swing.border.Border</tt>. Only
67   * <tt>JComponent</tt>s will show any indication with a border.
68   * <p/>
69   *
70   * <p>Original author: Robert Harder, rharder@usa.net</p>
71   *
72   * @author  Robert Harder
73   * @author  Lars K?hne
74   */
75  class FileDrop
76  {
77      // TODO: Not sure that changing borders is a good idea.
78      // At least we should make sure that the border insets are preserved so
79      // that the panel layout does not change during the DnD operation.
80  
81      private transient Border normalBorder;
82      private final transient DropTargetListener dropListener;
83  
84      // TODO: Blue is not a nice color in all LookAndFeels
85      /* Default border color */
86      private static final Color DEFAULT_BORDER_COLOR =
87              new Color(0f, 0f, 1f, 0.25f);
88  
89      /**
90       * Constructs a {@link FileDrop} with a default light-blue border
91       * and, if <var>c</var> is a {@link java.awt.Container}, recursively
92       * sets all elements contained within as drop targets, though only
93       * the top level container will change borders.
94       *
95       * @param c Component on which files will be dropped.
96       * @param listener Listens for <tt>filesDropped</tt>.
97       * @since 1.0
98       */
99      FileDrop(
100             final Component c,
101             final Listener listener)
102             throws TooManyListenersException
103     {
104         this(   c, // Drop target
105                 BorderFactory.createMatteBorder(2, 2, 2, 2, DEFAULT_BORDER_COLOR), // Drag border
106                 true, // Recursive
107                 listener);
108     }
109 
110 
111     /**
112      * Full constructor with a specified border and debugging optionally turned on.
113      * With Debugging turned on, more status messages will be displayed to
114      * <tt>out</tt>. A common way to use this constructor is with
115      * <tt>System.out</tt> or <tt>System.err</tt>. A <tt>null</tt> value for
116      * the parameter <tt>out</tt> will result in no debugging output.
117      *
118      * @param c Component on which files will be dropped.
119      * @param dragBorder Border to use on <tt>JComponent</tt> when dragging occurs.
120      * @param recursive Recursively set children as drop targets.
121      * @param listener Listens for <tt>filesDropped</tt>.
122      * @since 1.0
123      */
124     FileDrop(
125             final Component c,
126             final Border dragBorder,
127             final boolean recursive,
128             final Listener listener)
129             throws TooManyListenersException
130     {
131         dropListener = new FileDropTargetListener(c, dragBorder, listener);
132         makeDropTarget(c, recursive);
133     }
134 
135 
136     private void makeDropTarget(final Component c, boolean recursive)
137             throws TooManyListenersException
138     {
139         // Make drop target
140         final DropTarget dt = new DropTarget();
141         dt.addDropTargetListener(dropListener);
142 
143         // Listen for hierarchy changes and remove the
144         // drop target when the parent gets cleared out.
145         c.addHierarchyListener(new HierarchyListener()
146         {
147             @Override
148             public void hierarchyChanged(HierarchyEvent evt)
149             {
150                 final Component parent = c.getParent();
151                 if (parent == null) {
152                     c.setDropTarget(null);
153                 }
154                 else {
155                     new DropTarget(c, dropListener);
156                 }
157             }
158         });
159 
160         if (c.getParent() != null) {
161             new DropTarget(c, dropListener);
162         }
163 
164         if (recursive && (c instanceof Container)) {
165             final Container cont = (Container) c;
166             final Component[] comps = cont.getComponents();
167             for (Component element : comps)
168                 makeDropTarget(element, recursive);
169         }
170     }
171 
172 
173     /** Determine if the dragged data is a file list. */
174     private boolean isDragOk(final DropTargetDragEvent evt)
175     {
176         boolean ok = false;
177         final DataFlavor[] flavors = evt.getCurrentDataFlavors();
178 
179         // See if any of the flavors are a file list
180         int i = 0;
181         while (!ok && (i < flavors.length)) {   // Is the flavor a file list?
182             if (flavors[i].equals(DataFlavor.javaFileListFlavor))
183                 ok = true;
184             i++;
185         }
186 
187         return ok;
188     }
189 
190 
191     /**
192      * Removes the drag-and-drop hooks from the component and optionally
193      * from the all children. You should call this if you add and remove
194      * components after you've set up the drag-and-drop.
195      * This will recursively unregister all components contained within
196      * <var>c</var> if <var>c</var> is a {@link Container}.
197      *
198      * @param c The component to unregister as a drop target
199      * @since 1.0
200      */
201     static void remove(Component c)
202     {
203         remove(c, true);
204     }
205 
206 
207     /**
208      * Removes the drag-and-drop hooks from the component and optionally
209      * from the all children. You should call this if you add and remove
210      * components after you've set up the drag-and-drop.
211      *
212      * @param c The component to unregister
213      * @param recursive Recursively unregister components within a container
214      * @since 1.0
215      */
216     static void remove(Component c, boolean recursive)
217     {
218         c.setDropTarget(null);
219         if (recursive && (c instanceof Container)) {
220             final Component[] comps = ((Container) c).getComponents();
221             for (Component element : comps) {
222                 remove(element, recursive);
223             }
224         }
225     }
226 
227 
228     /**
229      * Implement this inner interface to listen for when files are dropped. For example
230      * your class declaration may begin like this:
231      * <code><pre>
232      *      public class MyClass implements FileDrop.Listener
233      *      ...
234      *      public void filesDropped( File[] files )
235      *      {
236      *          ...
237      *      }   // end filesDropped
238      *      ...
239      * </pre></code>
240      *
241      * @since 1.0
242      */
243     public interface Listener
244     {
245         /**
246          * This method is called when files have been successfully dropped.
247          *
248          * @param files An array of <tt>File</tt>s that were dropped.
249          * @since 1.0
250          */
251         void filesDropped(File[] files);
252     }
253 
254     private class FileDropTargetListener implements DropTargetListener
255     {
256         private final Component component;
257         private final Border dragBorder;
258         private final Listener listener;
259 
260         @Override
261         public void dragEnter(DropTargetDragEvent evt)
262         {
263             if (isDragOk(evt)) {
264                 if (component instanceof JComponent) {
265                     final JComponent jc = (JComponent) component;
266                     normalBorder = jc.getBorder();
267                     jc.setBorder(dragBorder);
268                 }
269                 evt.acceptDrag(DnDConstants.ACTION_COPY);
270             }
271             else {
272                 evt.rejectDrag();
273             }
274         }
275 
276         @Override
277         @SuppressWarnings("unchecked")
278         public void drop(DropTargetDropEvent evt)
279         {
280             try {
281                 final Transferable tr = evt.getTransferable();
282 
283                 if (tr.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
284                     evt.acceptDrop(DnDConstants.ACTION_COPY);
285 
286                     final List<File> fileList = (List<File>) tr.getTransferData(
287                             DataFlavor.javaFileListFlavor);
288                     final File[] files = new File[fileList.size()];
289                     fileList.toArray(files);
290 
291                     if (listener != null) {
292                         listener.filesDropped(files);
293                     }
294 
295                     evt.getDropTargetContext().dropComplete(true);
296                 }
297                 else {
298                     evt.rejectDrop();
299                 }
300             }
301             catch (final IOException io) {
302                 evt.rejectDrop();
303             }
304             catch (final UnsupportedFlavorException ufe) {
305                 evt.rejectDrop();
306             }
307             finally {
308                 if (component instanceof JComponent) {
309                     final JComponent jc = (JComponent) component;
310                     jc.setBorder(normalBorder);
311                 }
312             }
313         }
314 
315         @Override
316         public void dragExit(DropTargetEvent evt)
317         {
318             if (component instanceof JComponent) {
319                 final JComponent jc = (JComponent) component;
320                 jc.setBorder(normalBorder);
321             }
322         }
323 
324         @Override
325         public void dropActionChanged(DropTargetDragEvent evt)
326         {
327             if (isDragOk(evt)) {
328                 evt.acceptDrag(DnDConstants.ACTION_COPY);
329             }
330             else {
331                 evt.rejectDrag();
332             }
333         }
334 
335         @Override
336         public void dragOver(DropTargetDragEvent dtde)
337         {
338         }
339 
340         public FileDropTargetListener(Component component, Border dragBorder, Listener listener)
341         {
342             this.component = component;
343             this.dragBorder = dragBorder;
344             this.listener = listener;
345         }
346     }
347 
348 }