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 }