/* Copyright (c) 2010, Carl Burch. License information is located in the
* com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
package com.cburch.logisim.util;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
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.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.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.tree.TreePath;
/* This comes from "Denis" at http://forum.java.sun.com/thread.jspa?forumID=57&threadID=296255 */
/*
* My program use 4 classes. The application displays two trees.
* You can drag (move by default or copy with ctrl pressed) a node from the left tree to the right one, from the right to the left and inside the same tree.
* The rules for moving are :
* - you can't move the root
* - you can't move the selected node to its subtree (in the same tree).
* - you can't move the selected node to itself (in the same tree).
* - you can't move the selected node to its parent (in the same tree).
* - you can move a node to anywhere you want according to the 4 previous rules.
* The rules for copying are :
* - you can copy a node to anywhere you want.
*
* In the implementation I used DnD version of Java 1.3 because in 1.4 the DnD is too restrictive :
* you can't do what you want (displaying the image of the node while dragging, changing the cursor
* according to where you are dragging, etc...). In 1.4, the DnD is based on the 1.3 version but
* it is too encapsulated.
*/
public class JTreeUtil {
private static final Insets DEFAULT_INSETS = new Insets(20, 20, 20, 20);
private static final DataFlavor NODE_FLAVOR = new DataFlavor(
DataFlavor.javaJVMLocalObjectMimeType, "Node");
private static Object draggedNode;
private static BufferedImage image = null; // buff image
private static class TransferableNode implements Transferable {
private Object node;
private DataFlavor[] flavors = { NODE_FLAVOR };
public TransferableNode(Object nd) {
node = nd;
}
public synchronized Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException {
if (flavor == NODE_FLAVOR) {
return node;
} else {
throw new UnsupportedFlavorException(flavor);
}
}
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return Arrays.asList(flavors).contains(flavor);
}
}
/*
* This class is the most important. It manages all the DnD behavior. It is
* abstract because it contains two abstract methods:
* public abstract boolean canPerformAction(JTree target,
* Object draggedNode, int action, Point location);
* public abstract boolean executeDrop(DNDTree tree,
* Object draggedNode, Object newParentNode, int action);
* we have to override to give the required behavior of DnD in your tree.
*/
private static class TreeTransferHandler implements
DragGestureListener, DragSourceListener, DropTargetListener {
private JTree tree;
private JTreeDragController controller;
private DragSource dragSource; // dragsource
private Rectangle rect2D = new Rectangle();
private boolean drawImage;
protected TreeTransferHandler(JTree tree, JTreeDragController controller,
int action, boolean drawIcon) {
this.tree = tree;
this.controller = controller;
drawImage = drawIcon;
dragSource = new DragSource();
dragSource.createDefaultDragGestureRecognizer(tree, action, this);
}
/* Methods for DragSourceListener */
public void dragDropEnd(DragSourceDropEvent dsde) {
/*
if (dsde.getDropSuccess()
&& dsde.getDropAction() == DnDConstants.ACTION_MOVE
&& draggedNodeParent != null) {
((DefaultTreeModel) tree.getModel())
.nodeStructureChanged(draggedNodeParent);
}
*/
}
public final void dragEnter(DragSourceDragEvent dsde) {
int action = dsde.getDropAction();
if (action == DnDConstants.ACTION_COPY) {
dsde.getDragSourceContext().setCursor(
DragSource.DefaultCopyDrop);
} else {
if (action == DnDConstants.ACTION_MOVE) {
dsde.getDragSourceContext().setCursor(
DragSource.DefaultMoveDrop);
} else {
dsde.getDragSourceContext().setCursor(
DragSource.DefaultMoveNoDrop);
}
}
}
public final void dragOver(DragSourceDragEvent dsde) {
int action = dsde.getDropAction();
if (action == DnDConstants.ACTION_COPY) {
dsde.getDragSourceContext().setCursor(
DragSource.DefaultCopyDrop);
} else {
if (action == DnDConstants.ACTION_MOVE) {
dsde.getDragSourceContext().setCursor(
DragSource.DefaultMoveDrop);
} else {
dsde.getDragSourceContext().setCursor(
DragSource.DefaultMoveNoDrop);
}
}
}
public final void dropActionChanged(DragSourceDragEvent dsde) {
int action = dsde.getDropAction();
if (action == DnDConstants.ACTION_COPY) {
dsde.getDragSourceContext().setCursor(
DragSource.DefaultCopyDrop);
} else {
if (action == DnDConstants.ACTION_MOVE) {
dsde.getDragSourceContext().setCursor(
DragSource.DefaultMoveDrop);
} else {
dsde.getDragSourceContext().setCursor(
DragSource.DefaultMoveNoDrop);
}
}
}
public final void dragExit(DragSourceEvent dse) {
dse.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
}
/* Methods for DragGestureListener */
public final void dragGestureRecognized(DragGestureEvent dge) {
TreePath path = tree.getSelectionPath();
if (path != null) {
draggedNode = path.getLastPathComponent();
if (drawImage) {
Rectangle pathBounds = tree.getPathBounds(path); // getpathbounds
// of
// selectionpath
JComponent lbl = (JComponent) tree
.getCellRenderer()
.getTreeCellRendererComponent(
tree,
draggedNode,
false,
tree.isExpanded(path),
tree.getModel() .isLeaf(path.getLastPathComponent()),
0, false);// returning the label
lbl.setBounds(pathBounds);// setting bounds to lbl
image = new BufferedImage(lbl.getWidth(), lbl.getHeight(),
java.awt.image.BufferedImage.TYPE_INT_ARGB_PRE);// buffered
// image
// reference
// passing
// the
// label's
// ht
// and
// width
Graphics2D graphics = image.createGraphics();// creating
// the
// graphics
// for
// buffered
// image
graphics.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 0.5f)); // Sets the
// Composite for
// the
// Graphics2D
// context
lbl.setOpaque(false);
lbl.paint(graphics); // painting the graphics to label
graphics.dispose();
}
dragSource.startDrag(dge, DragSource.DefaultMoveNoDrop, image,
new Point(0, 0), new TransferableNode(draggedNode),
this);
}
}
/* Methods for DropTargetListener */
public final void dragEnter(DropTargetDragEvent dtde) {
Point pt = dtde.getLocation();
int action = dtde.getDropAction();
if (drawImage) {
paintImage(pt);
}
if (controller.canPerformAction(tree, draggedNode, action, pt)) {
dtde.acceptDrag(action);
} else {
dtde.rejectDrag();
}
}
public final void dragExit(DropTargetEvent dte) {
if (drawImage) {
clearImage();
}
}
public final void dragOver(DropTargetDragEvent dtde) {
Point pt = dtde.getLocation();
int action = dtde.getDropAction();
autoscroll(tree, pt);
if (drawImage) {
paintImage(pt);
}
if (controller.canPerformAction(tree, draggedNode, action, pt)) {
dtde.acceptDrag(action);
} else {
dtde.rejectDrag();
}
}
public final void dropActionChanged(DropTargetDragEvent dtde) {
Point pt = dtde.getLocation();
int action = dtde.getDropAction();
if (drawImage) {
paintImage(pt);
}
if (controller.canPerformAction(tree, draggedNode, action, pt)) {
dtde.acceptDrag(action);
} else {
dtde.rejectDrag();
}
}
public final void drop(DropTargetDropEvent dtde) {
try {
if (drawImage) {
clearImage();
}
int action = dtde.getDropAction();
Transferable transferable = dtde.getTransferable();
Point pt = dtde.getLocation();
if (transferable
.isDataFlavorSupported(NODE_FLAVOR)
&& controller.canPerformAction(tree, draggedNode, action, pt)) {
TreePath pathTarget = tree.getPathForLocation(pt.x, pt.y);
Object node = transferable.getTransferData(NODE_FLAVOR);
Object newParentNode = pathTarget.getLastPathComponent();
if (controller.executeDrop(tree, node, newParentNode, action)) {
dtde.acceptDrop(action);
dtde.dropComplete(true);
return;
}
}
dtde.rejectDrop();
dtde.dropComplete(false);
} catch (Exception e) {
dtde.rejectDrop();
dtde.dropComplete(false);
}
}
private final void paintImage(Point pt) {
tree.paintImmediately(rect2D.getBounds());
rect2D.setRect((int) pt.getX(), (int) pt.getY(), image.getWidth(),
image.getHeight());
tree.getGraphics().drawImage(image, (int) pt.getX(),
(int) pt.getY(), tree);
}
private final void clearImage() {
tree.paintImmediately(rect2D.getBounds());
}
}
public static void configureDragAndDrop(JTree tree, JTreeDragController controller) {
tree.setAutoscrolls(true);
new TreeTransferHandler(tree, controller, DnDConstants.ACTION_COPY_OR_MOVE, true);
}
private static void autoscroll(JTree tree, Point cursorLocation) {
Insets insets = DEFAULT_INSETS;
Rectangle outer = tree.getVisibleRect();
Rectangle inner = new Rectangle(outer.x + insets.left, outer.y
+ insets.top, outer.width - (insets.left + insets.right),
outer.height - (insets.top + insets.bottom));
if (!inner.contains(cursorLocation)) {
Rectangle scrollRect = new Rectangle(cursorLocation.x
- insets.left, cursorLocation.y - insets.top,
insets.left + insets.right, insets.top + insets.bottom);
tree.scrollRectToVisible(scrollRect);
}
}
}