FileTreeDragSource.java Source code

Java tutorial

Introduction

Here is the source code for FileTreeDragSource.java

Source

/*
Core SWING Advanced Programming 
By Kim Topley
ISBN: 0 13 083292 8       
Publisher: Prentice Hall  
*/

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.dnd.Autoscroll;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
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 FileTreeDragSource implements DragGestureListener, DragSourceListener {
    public FileTreeDragSource(FileTree tree) {
        this.tree = tree;

        // Use the default DragSource
        DragSource dragSource = DragSource.getDefaultDragSource();

        // Create a DragGestureRecognizer and
        // register as the listener
        dragSource.createDefaultDragGestureRecognizer(tree, DnDConstants.ACTION_COPY_OR_MOVE, this);
    }

    // Implementation of DragGestureListener interface.
    public void dragGestureRecognized(DragGestureEvent dge) {
        // Get the mouse location and convert it to
        // a location within the tree.
        Point location = dge.getDragOrigin();
        TreePath dragPath = tree.getPathForLocation(location.x, location.y);
        if (dragPath != null && tree.isPathSelected(dragPath)) {
            // Get the list of selected files and create a Transferable
            // The list of files and the is saved for use when
            // the drop completes.
            paths = tree.getSelectionPaths();
            if (paths != null && paths.length > 0) {
                dragFiles = new File[paths.length];
                for (int i = 0; i < paths.length; i++) {
                    String pathName = tree.getPathName(paths[i]);
                    dragFiles[i] = new File(pathName);
                }

                Transferable transferable = new FileListTransferable(dragFiles);
                dge.startDrag(null, transferable, this);
            }
        }
    }

    // Implementation of DragSourceListener interface
    public void dragEnter(DragSourceDragEvent dsde) {
        DnDUtils.debugPrintln(
                "Drag Source: dragEnter, drop action = " + DnDUtils.showActions(dsde.getDropAction()));
    }

    public void dragOver(DragSourceDragEvent dsde) {
        DnDUtils.debugPrintln("Drag Source: dragOver, drop action = " + DnDUtils.showActions(dsde.getDropAction()));
    }

    public void dragExit(DragSourceEvent dse) {
        DnDUtils.debugPrintln("Drag Source: dragExit");
    }

    public void dropActionChanged(DragSourceDragEvent dsde) {
        DnDUtils.debugPrintln(
                "Drag Source: dropActionChanged, drop action = " + DnDUtils.showActions(dsde.getDropAction()));
    }

    public void dragDropEnd(DragSourceDropEvent dsde) {
        DnDUtils.debugPrintln("Drag Source: drop completed, drop action = "
                + DnDUtils.showActions(dsde.getDropAction()) + ", success: " + dsde.getDropSuccess());
        // If the drop action was ACTION_MOVE,
        // the tree might need to be updated.
        if (dsde.getDropAction() == DnDConstants.ACTION_MOVE) {
            final File[] draggedFiles = dragFiles;
            final TreePath[] draggedPaths = paths;

            Timer tm = new Timer(200, new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    // Check whether each of the dragged files exists.
                    // If it does not, we need to remove the node
                    // that represents it from the tree.
                    for (int i = 0; i < draggedFiles.length; i++) {
                        if (draggedFiles[i].exists() == false) {
                            // Remove this node
                            DefaultMutableTreeNode node = (DefaultMutableTreeNode) draggedPaths[i]
                                    .getLastPathComponent();
                            ((DefaultTreeModel) tree.getModel()).removeNodeFromParent(node);
                        }
                    }
                }
            });
            tm.setRepeats(false);
            tm.start();
        }
    }

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        } catch (Exception evt) {
        }

        JFrame f = new JFrame("Draggable File Tree");
        try {
            FileTree tree = new FileTree("D:\\");
            f.getContentPane().add(new JScrollPane(tree));

            // Attach the drag source
            FileTreeDragSource dragSource = new FileTreeDragSource(tree);
        } catch (Exception e) {
        }
        f.pack();
        f.setVisible(true);
    }

    protected FileTree tree; // The associated tree

    protected File[] dragFiles; // Dragged files

    protected TreePath[] paths; // Dragged paths
}

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 FileListTransferable implements Transferable {
    public FileListTransferable(File[] files) {
        fileList = new ArrayList();
        for (int i = 0; i < files.length; i++) {
            fileList.add(files[i]);
        }
    }

    // Implementation of the Transferable interface
    public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[] { DataFlavor.javaFileListFlavor };
    }

    public boolean isDataFlavorSupported(DataFlavor fl) {
        return fl.equals(DataFlavor.javaFileListFlavor);
    }

    public Object getTransferData(DataFlavor fl) {
        if (!isDataFlavorSupported(fl)) {
            return null;
        }

        return fileList;
    }

    List fileList; // The list of files
}

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);
}