/Users/lyon/j4p/src/gui/tree/FileTreeDropTarget.java
|
1 package gui.tree;
2
3 import javax.swing.*;
4 import javax.swing.tree.TreePath;
5 import java.awt.*;
6 import java.awt.datatransfer.DataFlavor;
7 import java.awt.datatransfer.Transferable;
8 import java.awt.datatransfer.UnsupportedFlavorException;
9 import java.awt.dnd.*;
10 import java.awt.event.ActionEvent;
11 import java.awt.event.ActionListener;
12 import java.awt.event.WindowAdapter;
13 import java.awt.event.WindowEvent;
14 import java.beans.PropertyChangeEvent;
15 import java.beans.PropertyChangeListener;
16 import java.io.*;
17 import java.net.MalformedURLException;
18 import java.util.*;
19 import java.util.List;
20
21 public class FileTreeDropTarget implements DropTargetListener,
22 PropertyChangeListener {
23 public FileTreeDropTarget(FileTree tree) {
24 this.tree = tree;
25
26 // Listen for changes in the enabled property
27 tree.addPropertyChangeListener(this);
28
29 // Create the DropTarget and register
30 // it with the FileTree.
31 dropTarget = new DropTarget(tree,
32 DnDConstants.ACTION_COPY_OR_MOVE,
33 this,
34 tree.isEnabled(), null);
35 }
36
37 // Implementation of the DropTargetListener interface
38 public void dragEnter(DropTargetDragEvent dtde) {
39 DnDUtils.debugPrintln("dragEnter, drop action = "
40 + DnDUtils.showActions(dtde.getDropAction()));
41
42 // Save the list of selected items
43 saveTreeSelection();
44
45 // Get the type of object being transferred and determine
46 // whether it is appropriate.
47 checkTransferType(dtde);
48
49 // Accept or reject the drag.
50 boolean acceptedDrag = acceptOrRejectDrag(dtde);
51
52 // Do drag-under feedback
53 dragUnderFeedback(dtde, acceptedDrag);
54 }
55
56 public void dragExit(DropTargetEvent dte) {
57 DnDUtils.debugPrintln("DropTarget dragExit");
58
59 // Do drag-under feedback
60 dragUnderFeedback(null, false);
61
62 // Restore the original selections
63 restoreTreeSelection();
64 }
65
66 public void dragOver(DropTargetDragEvent dtde) {
67 DnDUtils.debugPrintln("DropTarget dragOver, drop action = "
68 + DnDUtils.showActions(dtde.getDropAction()));
69
70 // Accept or reject the drag
71 boolean acceptedDrag = acceptOrRejectDrag(dtde);
72
73 // Do drag-under feedback
74 dragUnderFeedback(dtde, acceptedDrag);
75 }
76
77 public void dropActionChanged(DropTargetDragEvent dtde) {
78 DnDUtils.debugPrintln("DropTarget dropActionChanged, drop action = "
79 + DnDUtils.showActions(dtde.getDropAction()));
80
81 // Accept or reject the drag
82 boolean acceptedDrag = acceptOrRejectDrag(dtde);
83
84 // Do drag-under feedback
85 dragUnderFeedback(dtde, acceptedDrag);
86 }
87
88 public void drop(DropTargetDropEvent dtde) {
89 DnDUtils.debugPrintln("DropTarget drop, drop action = "
90 + DnDUtils.showActions(dtde.getDropAction()));
91
92 // Check the drop action
93 if ((dtde.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) != 0) {
94 // Accept the drop and get the transfer data
95 dtde.acceptDrop(dtde.getDropAction());
96 Transferable transferable = dtde.getTransferable();
97 boolean dropSucceeded = false;
98
99 try {
100 tree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
101
102 // Save the user's selections
103 saveTreeSelection();
104
105 dropSucceeded = dropFile(dtde.getDropAction(),
106 transferable, dtde.getLocation());
107
108 DnDUtils.debugPrintln("Drop completed, success: "
109 + dropSucceeded);
110 } catch (Exception e) {
111 DnDUtils.debugPrintln("Exception while handling drop " + e);
112 } finally {
113 tree.setCursor(Cursor.getDefaultCursor());
114
115 // Restore the user's selections
116 restoreTreeSelection();
117 dtde.dropComplete(dropSucceeded);
118 }
119 } else {
120 DnDUtils.debugPrintln("Drop target rejected drop");
121 dtde.dropComplete(false);
122 }
123 }
124
125 // PropertyChangeListener interface
126 public void propertyChange(PropertyChangeEvent evt) {
127 String propertyName = evt.getPropertyName();
128 if (propertyName.equals("enabled")) {
129 // Enable the drop target if the FileTree is enabled
130 // and vice versa.
131 dropTarget.setActive(tree.isEnabled());
132 }
133 }
134
135 // Internal methods start here
136
137 protected boolean acceptOrRejectDrag(DropTargetDragEvent dtde) {
138 int dropAction = dtde.getDropAction();
139 int sourceActions = dtde.getSourceActions();
140 boolean acceptedDrag = false;
141
142 DnDUtils.debugPrintln("\tSource actions are " +
143 DnDUtils.showActions(sourceActions) +
144 ", drop action is " +
145 DnDUtils.showActions(dropAction));
146
147 Point location = dtde.getLocation();
148 boolean acceptableDropLocation = isAcceptableDropLocation(location);
149
150 // Reject if the object being transferred
151 // or the operations available are not acceptable.
152 if (!acceptableType ||
153 (sourceActions & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
154 DnDUtils.debugPrintln("Drop target rejecting drag");
155 dtde.rejectDrag();
156 } else if (!tree.isEditable()) {
157 // Can't drag to a read-only FileTree
158 DnDUtils.debugPrintln("Drop target rejecting drag");
159 dtde.rejectDrag();
160 } else if (!acceptableDropLocation) {
161 // Can only drag to writable directory
162 DnDUtils.debugPrintln("Drop target rejecting drag");
163 dtde.rejectDrag();
164 } else if ((dropAction & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
165 // Not offering copy or move - suggest a copy
166 DnDUtils.debugPrintln("Drop target offering COPY");
167 dtde.acceptDrag(DnDConstants.ACTION_COPY);
168 acceptedDrag = true;
169 } else {
170 // Offering an acceptable operation: accept
171 DnDUtils.debugPrintln("Drop target accepting drag");
172 dtde.acceptDrag(dropAction);
173 acceptedDrag = true;
174 }
175
176 return acceptedDrag;
177 }
178
179 protected void dragUnderFeedback(DropTargetDragEvent dtde,
180 boolean acceptedDrag) {
181 if (dtde != null && acceptedDrag) {
182 Point location = dtde.getLocation();
183 if (isAcceptableDropLocation(location)) {
184 tree.setSelectionRow(
185 tree.getRowForLocation(location.x, location.y));
186 } else {
187 tree.clearSelection();
188 }
189 } else {
190 tree.clearSelection();
191 }
192 }
193
194 protected void checkTransferType(DropTargetDragEvent dtde) {
195 // Accept a list of files
196 acceptableType = false;
197 if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
198 acceptableType = true;
199 }
200 DnDUtils.debugPrintln("Data type acceptable - " + acceptableType);
201 }
202
203 // This method handles a drop for a list of files
204 protected boolean dropFile(int action,
205 Transferable transferable, Point location)
206 throws IOException, UnsupportedFlavorException,
207 MalformedURLException {
208 List files = (List) transferable.getTransferData(
209 DataFlavor.javaFileListFlavor);
210 TreePath treePath = tree.getPathForLocation(
211 location.x, location.y);
212 File targetDirectory = findTargetDirectory(location);
213 if (treePath == null || targetDirectory == null) {
214 return false;
215 }
216 FileTree.DefaultMutableFileTreeNode node =
217 (FileTree.DefaultMutableFileTreeNode) treePath.getLastPathComponent();
218
219 // Highlight the drop location while we perform the drop
220 tree.setSelectionPath(treePath);
221
222 // Get File objects for all files being
223 // transferred, eliminating duplicates.
224 File[] fileList = getFileList(files);
225
226 // Don't overwrite files by default
227 copyOverExistingFiles = false;
228
229 // Copy or move each source object to the target
230 for (int i = 0; i < fileList.length; i++) {
231 File f = fileList[i];
232 if (f.isDirectory()) {
233 transferDirectory(action, f, targetDirectory, node);
234 } else {
235 try {
236 transferFile(action, fileList[i],
237 targetDirectory, node);
238 } catch (IllegalStateException e) {
239 // Cancelled by user
240 return false;
241 }
242 }
243 }
244
245 return true;
246 }
247
248 protected File findTargetDirectory(Point location) {
249 TreePath treePath = tree.getPathForLocation(location.x, location.y);
250 if (treePath != null) {
251 FileTree.DefaultMutableFileTreeNode node =
252 (FileTree.DefaultMutableFileTreeNode) treePath.getLastPathComponent();
253 // Only allow a drop on a writable directory
254 if (node.isDir()) {
255 try {
256 File f = new File(node.getFullName());
257 if (f.canWrite()) {
258 return f;
259 }
260 } catch (Exception e) {
261 }
262 }
263 }
264 return null;
265 }
266
267 protected boolean isAcceptableDropLocation(Point location) {
268 return findTargetDirectory(location) != null;
269 }
270
271 protected void saveTreeSelection() {
272 selections = tree.getSelectionPaths();
273 leadSelection = tree.getLeadSelectionPath();
274 tree.clearSelection();
275 }
276
277 protected void restoreTreeSelection() {
278 tree.setSelectionPaths(selections);
279
280 // Restore the lead selection
281 if (leadSelection != null) {
282 tree.removeSelectionPath(leadSelection);
283 tree.addSelectionPath(leadSelection);
284 }
285 }
286
287 // Get the list of files being transferred and
288 // remove any duplicates. For example, if the
289 // list contains /a/b/c and /a/b/c/d, the
290 // second entry is removed.
291 protected File[] getFileList(List files) {
292 int size = files.size();
293
294 // Get the files into an array for sorting
295 File[] f = new File[size];
296 Iterator iter = files.iterator();
297 int count = 0;
298 while (iter.hasNext()) {
299 f[count++] = (File) iter.next();
300 }
301
302 // Sort the files into alphabetical order
303 // based on pathnames.
304 Arrays.sort(f, new Comparator() {
305 public boolean equals(Object o1) {
306 return false;
307 }
308
309 public int compare(Object o1, Object o2) {
310 return ((File) o1).getAbsolutePath().compareTo(
311 ((File) o2).getAbsolutePath());
312 }
313 });
314
315 // Remove duplicates, retaining the results in a Vector
316 Vector v = new Vector();
317 char separator = System.getProperty("file.separator").charAt(0);
318 outer:
319 for (int i = f.length - 1; i >= 0; i--) {
320 String secondPath = f[i].getAbsolutePath();
321 int secondLength = secondPath.length();
322 for (int j = i - 1; j >= 0; j--) {
323 String firstPath = f[j].getAbsolutePath();
324 int firstLength = firstPath.length();
325 if (secondPath.startsWith(firstPath)
326 && firstLength != secondLength
327 && secondPath.charAt(firstLength) == separator) {
328 continue outer;
329 }
330 }
331 v.add(f[i]);
332 }
333
334 // Copy the retained files into an array
335 f = new File[v.size()];
336 v.copyInto(f);
337
338 return f;
339 }
340
341 // Copy or move a file
342 protected void transferFile(int action, File srcFile,
343 File targetDirectory,
344 FileTree.DefaultMutableFileTreeNode targetNode) {
345 DnDUtils.debugPrintln(
346 (action == DnDConstants.ACTION_COPY ? "Copy" : "Move") +
347 " file " + srcFile.getAbsolutePath() +
348 " to " + targetDirectory.getAbsolutePath());
349
350 // Create a File entry for the target
351 String name = srcFile.getName();
352 File newFile = new File(targetDirectory, name);
353 if (newFile.exists()) {
354 // Already exists - is it the same file?
355 if (newFile.equals(srcFile)) {
356 // Exactly the same file - ignore
357 return;
358 }
359 // File of this name exists in this directory
360 if (copyOverExistingFiles == false) {
361 int res = JOptionPane.showOptionDialog(tree,
362 "A file called\n " + name +
363 "\nalready exists in the directory\n " +
364 targetDirectory.getAbsolutePath() +
365 "\nOverwrite it?",
366 "File Exists",
367 JOptionPane.DEFAULT_OPTION,
368 JOptionPane.QUESTION_MESSAGE,
369 null, new String[]{
370 "Yes", "Yes to All", "No", "Cancel"
371 },
372 "No");
373 switch (res) {
374 case 1: // Yes to all
375 copyOverExistingFiles = true;
376 case 0: // Yes
377 break;
378 case 2: // No
379 return;
380 default: // Cancel
381 throw new IllegalStateException("Cancelled");
382 }
383 }
384 } else {
385 // New file - create it
386 try {
387 newFile.createNewFile();
388 } catch (IOException e) {
389 JOptionPane.showMessageDialog(tree,
390 "Failed to create new file\n " +
391 newFile.getAbsolutePath(),
392 "File Creation Failed",
393 JOptionPane.ERROR_MESSAGE);
394 return;
395 }
396 }
397
398 // Copy the data and close file.
399 BufferedInputStream is = null;
400 BufferedOutputStream os = null;
401
402 try {
403 is = new BufferedInputStream(
404 new FileInputStream(srcFile));
405 os = new BufferedOutputStream(
406 new FileOutputStream(newFile));
407 int size = 4096;
408 byte[] buffer = new byte[size];
409 int len;
410 while ((len = is.read(buffer, 0, size)) > 0) {
411 os.write(buffer, 0, len);
412 }
413 } catch (IOException e) {
414 JOptionPane.showMessageDialog(tree,
415 "Failed to copy file\n " +
416 name + "\nto directory\n " +
417 targetDirectory.getAbsolutePath(),
418 "File Copy Failed",
419 JOptionPane.ERROR_MESSAGE);
420 return;
421 } finally {
422 try {
423 if (is != null) {
424 is.close();
425 }
426 if (os != null) {
427 os.close();
428 }
429 } catch (IOException e) {
430 }
431 }
432
433 // Remove the source if this is a move operation.
434 if (action == DnDConstants.ACTION_MOVE &&
435 System.getProperty("DnDExamples.allowRemove") != null) {
436 srcFile.delete();
437 }
438
439 // Update the gui.tree display
440 if (targetNode != null) {
441 tree.addNode(targetNode, name);
442 }
443 }
444
445 protected void transferDirectory(int action, File srcDir,
446 File targetDirectory,
447 FileTree.DefaultMutableFileTreeNode targetNode) {
448 DnDUtils.debugPrintln(
449 (action == DnDConstants.ACTION_COPY ? "Copy" : "Move") +
450 " directory " + srcDir.getAbsolutePath() +
451 " to " + targetDirectory.getAbsolutePath());
452
453 // Do not copy a directory into itself or
454 // a subdirectory of itself.
455 File parentDir = targetDirectory;
456 while (parentDir != null) {
457 if (parentDir.equals(srcDir)) {
458 DnDUtils.debugPrintln("-- SUPPRESSED");
459 return;
460 }
461 parentDir = parentDir.getParentFile();
462 }
463
464 // Copy the directory itself, then its contents
465
466 // Create a File entry for the target
467 String name = srcDir.getName();
468 File newDir = new File(targetDirectory, name);
469 if (newDir.exists()) {
470 // Already exists - is it the same directory?
471 if (newDir.equals(srcDir)) {
472 // Exactly the same file - ignore
473 return;
474 }
475 } else {
476 // Directory does not exist - create it
477 if (newDir.mkdir() == false) {
478 // Failed to create - abandon this directory
479 JOptionPane.showMessageDialog(tree,
480 "Failed to create target directory\n " +
481 newDir.getAbsolutePath(),
482 "Directory creation Failed",
483 JOptionPane.ERROR_MESSAGE);
484 return;
485 }
486 }
487
488 // Add a node for the new directory
489 if (targetNode != null) {
490 targetNode = tree.addNode(targetNode, name);
491 }
492
493 // Now copy the directory content.
494 File[] files = srcDir.listFiles();
495 for (int i = 0; i < files.length; i++) {
496 File f = files[i];
497 if (f.isFile()) {
498 transferFile(action, f, newDir, targetNode);
499 } else if (f.isDirectory()) {
500 transferDirectory(action, f, newDir, targetNode);
501 }
502 }
503
504 // Remove the source directory after moving
505 if (action == DnDConstants.ACTION_MOVE &&
506 System.getProperty("DnDExamples.allowRemove") != null) {
507 srcDir.delete();
508 }
509 }
510
511 public static void main(String[] args) {
512 final JFrame f = new JFrame("FileTree Drop Target Example");
513
514 try {
515 final FileTree tree = new FileTree(args[0]);
516
517 // Add a drop target to the FileTree
518 FileTreeDropTarget target = new FileTreeDropTarget(tree);
519
520 tree.setEditable(true);
521
522 f.addWindowListener(new WindowAdapter() {
523 public void windowClosing(WindowEvent evt) {
524 System.exit(0);
525 }
526 });
527
528 JPanel panel = new JPanel();
529 final JCheckBox editable = new JCheckBox("Editable");
530 editable.setSelected(true);
531 panel.add(editable);
532 editable.addActionListener(new ActionListener() {
533 public void actionPerformed(ActionEvent evt) {
534 tree.setEditable(editable.isSelected());
535 }
536 });
537
538
539 final JCheckBox enabled = new JCheckBox("Enabled");
540 enabled.setSelected(true);
541 panel.add(enabled);
542 enabled.addActionListener(new ActionListener() {
543 public void actionPerformed(ActionEvent evt) {
544 tree.setEnabled(enabled.isSelected());
545 }
546 });
547
548 f.getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER);
549 f.getContentPane().add(panel, BorderLayout.SOUTH);
550 f.setSize(500, 400);
551 f.setVisible(true);
552 } catch (Exception e) {
553 System.out.println("Failed to build GUI: " + e);
554 }
555 }
556
557 protected FileTree tree;
558 protected DropTarget dropTarget;
559 protected boolean acceptableType; // Indicates whether data is acceptable
560 TreePath[] selections; // Initially selected rows
561 TreePath leadSelection; // Initial lead selection
562 boolean copyOverExistingFiles;
563 }
564
565
566