Java tutorial
/* Core SWING Advanced Programming By Kim Topley ISBN: 0 13 083292 8 Publisher: Prentice Hall */ import java.awt.BorderLayout; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.Autoscroll; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Vector; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; public class FileTreeDropTarget implements DropTargetListener, PropertyChangeListener { public FileTreeDropTarget(FileTree tree) { this.tree = tree; // Listen for changes in the enabled property tree.addPropertyChangeListener(this); // Create the DropTarget and register // it with the FileTree. dropTarget = new DropTarget(tree, DnDConstants.ACTION_COPY_OR_MOVE, this, tree.isEnabled(), null); } // Implementation of the DropTargetListener interface public void dragEnter(DropTargetDragEvent dtde) { DnDUtils.debugPrintln("dragEnter, drop action = " + DnDUtils.showActions(dtde.getDropAction())); // Save the list of selected items saveTreeSelection(); // Get the type of object being transferred and determine // whether it is appropriate. checkTransferType(dtde); // Accept or reject the drag. boolean acceptedDrag = acceptOrRejectDrag(dtde); // Do drag-under feedback dragUnderFeedback(dtde, acceptedDrag); } public void dragExit(DropTargetEvent dte) { DnDUtils.debugPrintln("DropTarget dragExit"); // Do drag-under feedback dragUnderFeedback(null, false); // Restore the original selections restoreTreeSelection(); } public void dragOver(DropTargetDragEvent dtde) { DnDUtils.debugPrintln("DropTarget dragOver, drop action = " + DnDUtils.showActions(dtde.getDropAction())); // Accept or reject the drag boolean acceptedDrag = acceptOrRejectDrag(dtde); // Do drag-under feedback dragUnderFeedback(dtde, acceptedDrag); } public void dropActionChanged(DropTargetDragEvent dtde) { DnDUtils.debugPrintln( "DropTarget dropActionChanged, drop action = " + DnDUtils.showActions(dtde.getDropAction())); // Accept or reject the drag boolean acceptedDrag = acceptOrRejectDrag(dtde); // Do drag-under feedback dragUnderFeedback(dtde, acceptedDrag); } public void drop(DropTargetDropEvent dtde) { DnDUtils.debugPrintln("DropTarget drop, drop action = " + DnDUtils.showActions(dtde.getDropAction())); // Check the drop action if ((dtde.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) != 0) { // Accept the drop and get the transfer data dtde.acceptDrop(dtde.getDropAction()); Transferable transferable = dtde.getTransferable(); boolean dropSucceeded = false; try { tree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); // Save the user's selections saveTreeSelection(); dropSucceeded = dropFile(dtde.getDropAction(), transferable, dtde.getLocation()); DnDUtils.debugPrintln("Drop completed, success: " + dropSucceeded); } catch (Exception e) { DnDUtils.debugPrintln("Exception while handling drop " + e); } finally { tree.setCursor(Cursor.getDefaultCursor()); // Restore the user's selections restoreTreeSelection(); dtde.dropComplete(dropSucceeded); } } else { DnDUtils.debugPrintln("Drop target rejected drop"); dtde.dropComplete(false); } } // PropertyChangeListener interface public void propertyChange(PropertyChangeEvent evt) { String propertyName = evt.getPropertyName(); if (propertyName.equals("enabled")) { // Enable the drop target if the FileTree is enabled // and vice versa. dropTarget.setActive(tree.isEnabled()); } } // Internal methods start here protected boolean acceptOrRejectDrag(DropTargetDragEvent dtde) { int dropAction = dtde.getDropAction(); int sourceActions = dtde.getSourceActions(); boolean acceptedDrag = false; DnDUtils.debugPrintln("\tSource actions are " + DnDUtils.showActions(sourceActions) + ", drop action is " + DnDUtils.showActions(dropAction)); Point location = dtde.getLocation(); boolean acceptableDropLocation = isAcceptableDropLocation(location); // Reject if the object being transferred // or the operations available are not acceptable. if (!acceptableType || (sourceActions & DnDConstants.ACTION_COPY_OR_MOVE) == 0) { DnDUtils.debugPrintln("Drop target rejecting drag"); dtde.rejectDrag(); } else if (!tree.isEditable()) { // Can't drag to a read-only FileTree DnDUtils.debugPrintln("Drop target rejecting drag"); dtde.rejectDrag(); } else if (!acceptableDropLocation) { // Can only drag to writable directory DnDUtils.debugPrintln("Drop target rejecting drag"); dtde.rejectDrag(); } else if ((dropAction & DnDConstants.ACTION_COPY_OR_MOVE) == 0) { // Not offering copy or move - suggest a copy DnDUtils.debugPrintln("Drop target offering COPY"); dtde.acceptDrag(DnDConstants.ACTION_COPY); acceptedDrag = true; } else { // Offering an acceptable operation: accept DnDUtils.debugPrintln("Drop target accepting drag"); dtde.acceptDrag(dropAction); acceptedDrag = true; } return acceptedDrag; } protected void dragUnderFeedback(DropTargetDragEvent dtde, boolean acceptedDrag) { if (dtde != null && acceptedDrag) { Point location = dtde.getLocation(); if (isAcceptableDropLocation(location)) { tree.setSelectionRow(tree.getRowForLocation(location.x, location.y)); } else { tree.clearSelection(); } } else { tree.clearSelection(); } } protected void checkTransferType(DropTargetDragEvent dtde) { // Accept a list of files acceptableType = false; if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { acceptableType = true; } DnDUtils.debugPrintln("Data type acceptable - " + acceptableType); } // This method handles a drop for a list of files protected boolean dropFile(int action, Transferable transferable, Point location) throws IOException, UnsupportedFlavorException, MalformedURLException { List files = (List) transferable.getTransferData(DataFlavor.javaFileListFlavor); TreePath treePath = tree.getPathForLocation(location.x, location.y); File targetDirectory = findTargetDirectory(location); if (treePath == null || targetDirectory == null) { return false; } FileTree.FileTreeNode node = (FileTree.FileTreeNode) treePath.getLastPathComponent(); // Highlight the drop location while we perform the drop tree.setSelectionPath(treePath); // Get File objects for all files being // transferred, eliminating duplicates. File[] fileList = getFileList(files); // Don't overwrite files by default copyOverExistingFiles = false; // Copy or move each source object to the target for (int i = 0; i < fileList.length; i++) { File f = fileList[i]; if (f.isDirectory()) { transferDirectory(action, f, targetDirectory, node); } else { try { transferFile(action, fileList[i], targetDirectory, node); } catch (IllegalStateException e) { // Cancelled by user return false; } } } return true; } protected File findTargetDirectory(Point location) { TreePath treePath = tree.getPathForLocation(location.x, location.y); if (treePath != null) { FileTree.FileTreeNode node = (FileTree.FileTreeNode) treePath.getLastPathComponent(); // Only allow a drop on a writable directory if (node.isDir()) { try { File f = new File(node.getFullName()); if (f.canWrite()) { return f; } } catch (Exception e) { } } } return null; } protected boolean isAcceptableDropLocation(Point location) { return findTargetDirectory(location) != null; } protected void saveTreeSelection() { selections = tree.getSelectionPaths(); leadSelection = tree.getLeadSelectionPath(); tree.clearSelection(); } protected void restoreTreeSelection() { tree.setSelectionPaths(selections); // Restore the lead selection if (leadSelection != null) { tree.removeSelectionPath(leadSelection); tree.addSelectionPath(leadSelection); } } // Get the list of files being transferred and // remove any duplicates. For example, if the // list contains /a/b/c and /a/b/c/d, the // second entry is removed. protected File[] getFileList(List files) { int size = files.size(); // Get the files into an array for sorting File[] f = new File[size]; Iterator iter = files.iterator(); int count = 0; while (iter.hasNext()) { f[count++] = (File) iter.next(); } // Sort the files into alphabetical order // based on pathnames. Arrays.sort(f, new Comparator() { public boolean equals(Object o1) { return false; } public int compare(Object o1, Object o2) { return ((File) o1).getAbsolutePath().compareTo(((File) o2).getAbsolutePath()); } }); // Remove duplicates, retaining the results in a Vector Vector v = new Vector(); char separator = System.getProperty("file.separator").charAt(0); outer: for (int i = f.length - 1; i >= 0; i--) { String secondPath = f[i].getAbsolutePath(); int secondLength = secondPath.length(); for (int j = i - 1; j >= 0; j--) { String firstPath = f[j].getAbsolutePath(); int firstLength = firstPath.length(); if (secondPath.startsWith(firstPath) && firstLength != secondLength && secondPath.charAt(firstLength) == separator) { continue outer; } } v.add(f[i]); } // Copy the retained files into an array f = new File[v.size()]; v.copyInto(f); return f; } // Copy or move a file protected void transferFile(int action, File srcFile, File targetDirectory, FileTree.FileTreeNode targetNode) { DnDUtils.debugPrintln((action == DnDConstants.ACTION_COPY ? "Copy" : "Move") + " file " + srcFile.getAbsolutePath() + " to " + targetDirectory.getAbsolutePath()); // Create a File entry for the target String name = srcFile.getName(); File newFile = new File(targetDirectory, name); if (newFile.exists()) { // Already exists - is it the same file? if (newFile.equals(srcFile)) { // Exactly the same file - ignore return; } // File of this name exists in this directory if (copyOverExistingFiles == false) { int res = JOptionPane.showOptionDialog(tree, "A file called\n " + name + "\nalready exists in the directory\n " + targetDirectory.getAbsolutePath() + "\nOverwrite it?", "File Exists", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[] { "Yes", "Yes to All", "No", "Cancel" }, "No"); switch (res) { case 1: // Yes to all copyOverExistingFiles = true; case 0: // Yes break; case 2: // No return; default: // Cancel throw new IllegalStateException("Cancelled"); } } } else { // New file - create it try { newFile.createNewFile(); } catch (IOException e) { JOptionPane.showMessageDialog(tree, "Failed to create new file\n " + newFile.getAbsolutePath(), "File Creation Failed", JOptionPane.ERROR_MESSAGE); return; } } // Copy the data and close file. BufferedInputStream is = null; BufferedOutputStream os = null; try { is = new BufferedInputStream(new FileInputStream(srcFile)); os = new BufferedOutputStream(new FileOutputStream(newFile)); int size = 4096; byte[] buffer = new byte[size]; int len; while ((len = is.read(buffer, 0, size)) > 0) { os.write(buffer, 0, len); } } catch (IOException e) { JOptionPane.showMessageDialog(tree, "Failed to copy file\n " + name + "\nto directory\n " + targetDirectory.getAbsolutePath(), "File Copy Failed", JOptionPane.ERROR_MESSAGE); return; } finally { try { if (is != null) { is.close(); } if (os != null) { os.close(); } } catch (IOException e) { } } // Remove the source if this is a move operation. if (action == DnDConstants.ACTION_MOVE && System.getProperty("DnDExamples.allowRemove") != null) { srcFile.delete(); } // Update the tree display if (targetNode != null) { tree.addNode(targetNode, name); } } protected void transferDirectory(int action, File srcDir, File targetDirectory, FileTree.FileTreeNode targetNode) { DnDUtils.debugPrintln((action == DnDConstants.ACTION_COPY ? "Copy" : "Move") + " directory " + srcDir.getAbsolutePath() + " to " + targetDirectory.getAbsolutePath()); // Do not copy a directory into itself or // a subdirectory of itself. File parentDir = targetDirectory; while (parentDir != null) { if (parentDir.equals(srcDir)) { DnDUtils.debugPrintln("-- SUPPRESSED"); return; } parentDir = parentDir.getParentFile(); } // Copy the directory itself, then its contents // Create a File entry for the target String name = srcDir.getName(); File newDir = new File(targetDirectory, name); if (newDir.exists()) { // Already exists - is it the same directory? if (newDir.equals(srcDir)) { // Exactly the same file - ignore return; } } else { // Directory does not exist - create it if (newDir.mkdir() == false) { // Failed to create - abandon this directory JOptionPane.showMessageDialog(tree, "Failed to create target directory\n " + newDir.getAbsolutePath(), "Directory creation Failed", JOptionPane.ERROR_MESSAGE); return; } } // Add a node for the new directory if (targetNode != null) { targetNode = tree.addNode(targetNode, name); } // Now copy the directory content. File[] files = srcDir.listFiles(); for (int i = 0; i < files.length; i++) { File f = files[i]; if (f.isFile()) { transferFile(action, f, newDir, targetNode); } else if (f.isDirectory()) { transferDirectory(action, f, newDir, targetNode); } } // Remove the source directory after moving if (action == DnDConstants.ACTION_MOVE && System.getProperty("DnDExamples.allowRemove") != null) { srcDir.delete(); } } public static void main(String[] args) { try { UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); } catch (Exception evt) { } final JFrame f = new JFrame("FileTree Drop Target Example"); try { final FileTree tree = new FileTree("D:\\"); // Add a drop target to the FileTree FileTreeDropTarget target = new FileTreeDropTarget(tree); tree.setEditable(true); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { System.exit(0); } }); JPanel panel = new JPanel(); final JCheckBox editable = new JCheckBox("Editable"); editable.setSelected(true); panel.add(editable); editable.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { tree.setEditable(editable.isSelected()); } }); final JCheckBox enabled = new JCheckBox("Enabled"); enabled.setSelected(true); panel.add(enabled); enabled.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { tree.setEnabled(enabled.isSelected()); } }); f.getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER); f.getContentPane().add(panel, BorderLayout.SOUTH); f.setSize(500, 400); f.setVisible(true); } catch (Exception e) { System.out.println("Failed to build GUI: " + e); } } protected FileTree tree; protected DropTarget dropTarget; protected boolean acceptableType; // Indicates whether data is acceptable TreePath[] selections; // Initially selected rows TreePath leadSelection; // Initial lead selection boolean copyOverExistingFiles; } class FileTree extends JTree implements Autoscroll { public static final Insets defaultScrollInsets = new Insets(8, 8, 8, 8); protected Insets scrollInsets = defaultScrollInsets; public FileTree(String path) throws FileNotFoundException, SecurityException { super((TreeModel) null); // Create the JTree itself // Use horizontal and vertical lines putClientProperty("JTree.lineStyle", "Angled"); // Create the first node FileTreeNode rootNode = new FileTreeNode(null, path); // Populate the root node with its subdirectories boolean addedNodes = rootNode.populateDirectories(true); setModel(new DefaultTreeModel(rootNode)); // Listen for Tree Selection Events addTreeExpansionListener(new TreeExpansionHandler()); } // Returns the full pathname for a path, or null if not a known path public String getPathName(TreePath path) { Object o = path.getLastPathComponent(); if (o instanceof FileTreeNode) { return ((FileTreeNode) o).fullName; } return null; } // Adds a new node to the tree after construction. // Returns the inserted node, or null if the parent // directory has not been expanded. public FileTreeNode addNode(FileTreeNode parent, String name) { int index = parent.addNode(name); if (index != -1) { ((DefaultTreeModel) getModel()).nodesWereInserted(parent, new int[] { index }); return (FileTreeNode) parent.getChildAt(index); } // No node was created return null; } // Autoscrolling support public void setScrollInsets(Insets insets) { this.scrollInsets = insets; } public Insets getScrollInsets() { return scrollInsets; } // Implementation of Autoscroll interface public Insets getAutoscrollInsets() { Rectangle r = getVisibleRect(); Dimension size = getSize(); Insets i = new Insets(r.y + scrollInsets.top, r.x + scrollInsets.left, size.height - r.y - r.height + scrollInsets.bottom, size.width - r.x - r.width + scrollInsets.right); return i; } public void autoscroll(Point location) { JScrollPane scroller = (JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, this); if (scroller != null) { JScrollBar hBar = scroller.getHorizontalScrollBar(); JScrollBar vBar = scroller.getVerticalScrollBar(); Rectangle r = getVisibleRect(); if (location.x <= r.x + scrollInsets.left) { // Need to scroll left hBar.setValue(hBar.getValue() - hBar.getUnitIncrement(-1)); } if (location.y <= r.y + scrollInsets.top) { // Need to scroll up vBar.setValue(vBar.getValue() - vBar.getUnitIncrement(-1)); } if (location.x >= r.x + r.width - scrollInsets.right) { // Need to scroll right hBar.setValue(hBar.getValue() + hBar.getUnitIncrement(1)); } if (location.y >= r.y + r.height - scrollInsets.bottom) { // Need to scroll down vBar.setValue(vBar.getValue() + vBar.getUnitIncrement(1)); } } } // Inner class that represents a node in this file system tree public static class FileTreeNode extends DefaultMutableTreeNode { public FileTreeNode(String parent, String name) throws SecurityException, FileNotFoundException { this.name = name; // See if this node exists and whether it is a directory fullName = parent == null ? name : parent + File.separator + name; File f = new File(fullName); if (f.exists() == false) { throw new FileNotFoundException("File " + fullName + " does not exist"); } isDir = f.isDirectory(); // Hack for Windows which doesn't consider a drive to be a directory! if (isDir == false && f.isFile() == false) { isDir = true; } } // Override isLeaf to check whether this is a directory public boolean isLeaf() { return !isDir; } // Override getAllowsChildren to check whether this is a directory public boolean getAllowsChildren() { return isDir; } // Return whether this is a directory public boolean isDir() { return isDir; } // Get full path public String getFullName() { return fullName; } // For display purposes, we return our own name public String toString() { return name; } // If we are a directory, scan our contents and populate // with children. In addition, populate those children // if the "descend" flag is true. We only descend once, // to avoid recursing the whole subtree. // Returns true if some nodes were added boolean populateDirectories(boolean descend) { boolean addedNodes = false; // Do this only once if (populated == false) { File f; try { f = new File(fullName); } catch (SecurityException e) { populated = true; return false; } if (interim == true) { // We have had a quick look here before: // remove the dummy node that we added last time removeAllChildren(); interim = false; } String[] names = f.list(); // Get list of contents // Process the contents ArrayList list = new ArrayList(); for (int i = 0; i < names.length; i++) { String name = names[i]; File d = new File(fullName, name); try { FileTreeNode node = new FileTreeNode(fullName, name); list.add(node); if (descend && d.isDirectory()) { node.populateDirectories(false); } addedNodes = true; if (descend == false) { // Only add one node if not descending break; } } catch (Throwable t) { // Ignore phantoms or access problems } } if (addedNodes == true) { // Now sort the list of contained files and directories Object[] nodes = list.toArray(); Arrays.sort(nodes, new Comparator() { public boolean equals(Object o) { return false; } public int compare(Object o1, Object o2) { FileTreeNode node1 = (FileTreeNode) o1; FileTreeNode node2 = (FileTreeNode) o2; // Directories come first if (node1.isDir != node2.isDir) { return node1.isDir ? -1 : +1; } // Both directories or both files - // compare based on pathname return node1.fullName.compareTo(node2.fullName); } }); // Add sorted items as children of this node for (int j = 0; j < nodes.length; j++) { this.add((FileTreeNode) nodes[j]); } } // If we were scanning to get all subdirectories, // or if we found no content, there is no // reason to look at this directory again, so // set populated to true. Otherwise, we set interim // so that we look again in the future if we need to if (descend == true || addedNodes == false) { populated = true; } else { // Just set interim state interim = true; } } return addedNodes; } // Adding a new file or directory after // constructing the FileTree. Returns // the index of the inserted node. public int addNode(String name) { // If not populated yet, do nothing if (populated == true) { // Do not add a new node if // the required node is already there int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { FileTreeNode node = (FileTreeNode) getChildAt(i); if (node.name.equals(name)) { // Already exists - ensure // we repopulate if (node.isDir()) { node.interim = true; node.populated = false; } return -1; } } // Add a new node try { FileTreeNode node = new FileTreeNode(fullName, name); add(node); return childCount; } catch (Exception e) { } } return -1; } protected String name; // Name of this component protected String fullName; // Full pathname protected boolean populated;// true if we have been populated protected boolean interim; // true if we are in interim state protected boolean isDir; // true if this is a directory } // Inner class that handles Tree Expansion Events protected class TreeExpansionHandler implements TreeExpansionListener { public void treeExpanded(TreeExpansionEvent evt) { TreePath path = evt.getPath(); // The expanded path JTree tree = (JTree) evt.getSource(); // The tree // Get the last component of the path and // arrange to have it fully populated. FileTreeNode node = (FileTreeNode) path.getLastPathComponent(); if (node.populateDirectories(true)) { ((DefaultTreeModel) tree.getModel()).nodeStructureChanged(node); } } public void treeCollapsed(TreeExpansionEvent evt) { // Nothing to do } } } class DnDUtils { public static String showActions(int action) { String actions = ""; if ((action & (DnDConstants.ACTION_LINK | DnDConstants.ACTION_COPY_OR_MOVE)) == 0) { return "None"; } if ((action & DnDConstants.ACTION_COPY) != 0) { actions += "Copy "; } if ((action & DnDConstants.ACTION_MOVE) != 0) { actions += "Move "; } if ((action & DnDConstants.ACTION_LINK) != 0) { actions += "Link"; } return actions; } public static boolean isDebugEnabled() { return debugEnabled; } public static void debugPrintln(String s) { if (debugEnabled) { System.out.println(s); } } private static boolean debugEnabled = (System.getProperty("DnDExamples.debug") != null); }