/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