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