/*
 * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *  o Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer. 
 *     
 *  o Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution. 
 *     
 *  o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of 
 *    its contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
package org.pushingpixels.lafwidget.tree.dnd;

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.EventListener;

import javax.swing.JTree;
import javax.swing.event.EventListenerList;
import javax.swing.tree.*;

import org.pushingpixels.lafwidget.*;

/**
 * TreeWrapper is used to handle drag and drop and popup menus in any JTree.
 * 
 * @author Antonio Vieiro (antonio@antonioshome.net), $Author: kirillcool $
 */
public class TreeDragAndDropWidget extends LafWidgetAdapter<JTree> {
	/**
	 * This to avoid excesive creation of objects in invocation.
	 */
	private static Class[] EMPTY_CLASS_ARRAY = new Class[0];

	/**
	 * This to avoid excessive reflection to find the "getTransferable" method.
	 */
	private static Method getTransferableMethod = null;

	/**
	 * This to avoid excesive creation of objects in invocation.
	 */
	private static Object[] EMPTY_OBJECT_ARRAY = new Object[0];

	protected DnDCellRendererProxy rendererProxy;

	protected DragSource dragSource;

	protected DropTarget dropTarget;

	protected MutableTreeNode dropNode;

	protected EventListenerList listeners;

	protected PropertyChangeListener propertyChangeListener;

	protected PropertyChangeListener cellRendererChangeListener;

	protected TreeDropTargetListener dropListener;

	protected TreeDragGestureListener gestureListener;

	protected DragGestureRecognizer dragGestureRecognizer;

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
	 */
	@Override
    public boolean requiresCustomLafSupport() {
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
	 */
	@Override
	public void installListeners() {
		this.listeners = new EventListenerList();

		this.propertyChangeListener = new PropertyChangeListener() {
			@Override
            public void propertyChange(PropertyChangeEvent evt) {
				if (LafWidget.TREE_AUTO_DND_SUPPORT.equals(evt
						.getPropertyName())) {
					Object oldValue = evt.getOldValue();
					Object newValue = evt.getNewValue();
					boolean hadDnd = false;
					if (oldValue instanceof Boolean)
						hadDnd = (Boolean) oldValue;
					boolean hasDnd = false;
					if (newValue instanceof Boolean)
						hasDnd = (Boolean) newValue;

					if (!hadDnd && hasDnd
							&& TreeDragAndDropWidget.this.jcomp.isEnabled()) {
						TreeDragAndDropWidget.this.installDnDSupport();
					}
					if (hadDnd && !hasDnd) {
						TreeDragAndDropWidget.this.uninstallDnDSupport();
					}
				}

				if ("enabled".equals(evt.getPropertyName())) {
					boolean wasEnabled = (Boolean) evt.getOldValue();
					boolean isEnabled = (Boolean) evt.getNewValue();
					if (!wasEnabled && isEnabled) {
						if (LafWidgetUtilities
								.hasAutomaticDnDSupport(TreeDragAndDropWidget.this.jcomp))
							TreeDragAndDropWidget.this.installDnDSupport();
					}
					if (wasEnabled && !isEnabled)
						TreeDragAndDropWidget.this.uninstallDnDSupport();
				}
			}
		};
		this.jcomp.addPropertyChangeListener(this.propertyChangeListener);

		if (this.jcomp.isEnabled()
				&& LafWidgetUtilities.hasAutomaticDnDSupport(this.jcomp))
			this.installDnDSupport();
	}

	private void installDnDSupport() {
		if (this.cellRendererChangeListener != null) {
			// already installed.
			return;
		}

		this.cellRendererChangeListener = new PropertyChangeListener() {
			@Override
            public void propertyChange(PropertyChangeEvent evt) {
				String name = evt.getPropertyName();

				if (name.equals(JTree.CELL_RENDERER_PROPERTY)) {
					TreeCellRenderer renderer = TreeDragAndDropWidget.this.jcomp
							.getCellRenderer();
					if (!(renderer instanceof DnDCellRendererProxy)) {
						TreeDragAndDropWidget.this.rendererProxy = new DnDCellRendererProxy(
								renderer);
						TreeDragAndDropWidget.this.jcomp
								.setCellRenderer(TreeDragAndDropWidget.this.rendererProxy);
						TreeDragAndDropWidget.this.jcomp.repaint();
					} else
						TreeDragAndDropWidget.this.rendererProxy = (DnDCellRendererProxy) renderer;
				}
			}
		};

		this.jcomp.addPropertyChangeListener(this.cellRendererChangeListener);
		if (this.jcomp.getCellRenderer() != null) {
			this.jcomp.setCellRenderer(new DnDCellRendererProxy(this.jcomp
					.getCellRenderer()));
		}
		this.dragSource = new DragSource();
		this.gestureListener = new TreeDragGestureListener();
		this.dragGestureRecognizer = this.dragSource
				.createDefaultDragGestureRecognizer(this.jcomp,
						DnDConstants.ACTION_COPY_OR_MOVE, this.gestureListener);
		this.dropListener = new TreeDropTargetListener();
		this.dropTarget = new AutoScrollingTreeDropTarget(this.jcomp,
				this.dropListener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
	 */
	@Override
	public void uninstallListeners() {
		this.jcomp.removePropertyChangeListener(this.propertyChangeListener);
		this.propertyChangeListener = null;

		this.uninstallDnDSupport();
	}

	private void uninstallDnDSupport() {
		if (this.cellRendererChangeListener != null) {
			this.jcomp
					.removePropertyChangeListener(this.cellRendererChangeListener);
			this.cellRendererChangeListener = null;
		}

		TreeCellRenderer tcl = this.jcomp.getCellRenderer();
		if (tcl instanceof DnDCellRendererProxy) {
			TreeCellRenderer origRenderer = ((DnDCellRendererProxy) tcl)
					.getOriginalTreeCellRenderer();
			this.jcomp.setCellRenderer(origRenderer);
		}

		if (this.dropListener != null) {
			this.dropTarget.removeDropTargetListener(this.dropListener);
			this.dropListener = null;
			this.jcomp.setDropTarget(null);
		}

		if (this.dragGestureRecognizer != null) {
			this.dragGestureRecognizer
					.removeDragGestureListener(this.gestureListener);
			this.gestureListener = null;
			this.dragGestureRecognizer = null;
		}
	}

	/**
	 * Internal class that implements DragGestureListener.
	 */
	class TreeDragGestureListener implements DragGestureListener {
		@Override
        public void dragGestureRecognized(DragGestureEvent dge) {
			// If tree is disabled then discard drag from it
			if (!TreeDragAndDropWidget.this.jcomp.isEnabled())
				return;

			// Select the node that the user is trying to drag, if any.
			TreePath draggedPath = TreeDragAndDropWidget.this.jcomp
					.getClosestPathForLocation(dge.getDragOrigin().x, dge
							.getDragOrigin().y);
			if (draggedPath == null)
				return;
			TreeNode node = (TreeNode) draggedPath.getLastPathComponent();
			if ((node instanceof MutableTreeNode) && (node.getParent() != null)
					&& (node.getParent() instanceof MutableTreeNode))
				;
			else
				return;

			// Prepare the node for transfer
			TransferableTreeNode transferableNode = new TransferableTreeNode(
					TreeDragAndDropWidget.this.jcomp, (MutableTreeNode) node,
					TreeDragAndDropWidget.this.jcomp.isExpanded(draggedPath));
			TreeDragAndDropWidget.this.rendererProxy.setDraggedNode(node);

			// Initialize the drag. If isDragImageSupported then build a
			// transparent image
			BufferedImage image = null;
			Point imageOffset = null;
			// Create an image with the dragged node.
			TreeCellRenderer renderer = TreeDragAndDropWidget.this.rendererProxy
					.getOriginalTreeCellRenderer();
			Rectangle dragBounds = TreeDragAndDropWidget.this.jcomp
					.getPathBounds(draggedPath);
			imageOffset = new Point(dge.getDragOrigin().x - dragBounds.x, dge
					.getDragOrigin().y
					- dragBounds.y);
			Component component = renderer.getTreeCellRendererComponent(
					TreeDragAndDropWidget.this.jcomp, node, false,
					TreeDragAndDropWidget.this.jcomp.isExpanded(draggedPath),
					node.isLeaf(), 0, false);
			component.setSize(dragBounds.width, dragBounds.height);
			image = new BufferedImage(dragBounds.width, dragBounds.height,
					BufferedImage.TYPE_INT_ARGB);
			Graphics2D g2d = image.createGraphics();
			g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC,
					0.75f));
			component.paint(g2d);
			g2d.dispose();

			// Initiate the drag
			if (DragSource.isDragImageSupported()) {
				TreeDragAndDropWidget.this.dragSource.startDrag(dge, null,
						image, imageOffset, transferableNode,
						new TreeDragSourceListener());
			} else {
				TreeDragAndDropWidget.this.dragSource.startDrag(dge, null,
						transferableNode, new TreeDragSourceListener());
			}
		}
	}

	/**
	 * Internal class that implements DragSourceListener.
	 */
	class TreeDragSourceListener implements DragSourceListener {
		@Override
        public void dragExit(DragSourceEvent dse) {
			// dropNode = null;
			// rendererProxy.setDropNode( null );
			// tree.repaint();
		}

		@Override
        public void dropActionChanged(DragSourceDragEvent dsde) {
		}

		@Override
        public void dragOver(DragSourceDragEvent dsde) {
		}

		@Override
        public void dragEnter(DragSourceDragEvent dsde) {
		}

		@Override
        public void dragDropEnd(DragSourceDropEvent dsde) {
			TreeDragAndDropWidget.this.resetDragAndDrop();
		}
	}

	class TreeDropTargetListener implements DropTargetListener {
		@Override
        public void drop(DropTargetDropEvent dtde) {
			TreePath dropPath = TreeDragAndDropWidget.this.jcomp
					.getClosestPathForLocation(dtde.getLocation().x, dtde
							.getLocation().y);

			if (!TreeDragAndDropWidget.this.jcomp.isEnabled()
					|| (dropPath == null)) {
				dtde.rejectDrop();
				dtde.dropComplete(false);
				TreeDragAndDropWidget.this.resetDragAndDrop();
				return;
			}

			TreeDragAndDropWidget.this.dropNode = (MutableTreeNode) dropPath
					.getLastPathComponent();

			// Handle dropping java JVM local objects (tree nodes)
			try {
				dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
				// Are we dropping a JVM local object?
				TransferableTreeNode ttn = (TransferableTreeNode) dtde
						.getTransferable().getTransferData(
								TransferableTreeNode
										.getJavaJVMLocalObjectFlavor());
				JTree sourceTree = ttn.getSourceTree();
				MutableTreeNode sourceNode = ttn.getSourceNode();
				if (TreeDragAndDropWidget.this.mayDropHere(sourceTree,
						sourceNode, TreeDragAndDropWidget.this.dropNode)) {
					dtde
							.dropComplete(this
									.dropNodes(
											ttn.getSourceTree(),
											ttn.getSourceNode(),
											TreeDragAndDropWidget.this.jcomp,
											TreeDragAndDropWidget.this.dropNode,
											(dtde.getDropAction() & DnDConstants.ACTION_MOVE) != 0));
					if (ttn.isNodeWasExpanded()) {
						DefaultTreeModel targetModel = (DefaultTreeModel) TreeDragAndDropWidget.this.jcomp
								.getModel();
						TreeDragAndDropWidget.this.jcomp
								.expandPath(new TreePath(targetModel
										.getPathToRoot(ttn.getSourceNode())));
					}

					TreeDragAndDropWidget.this.resetDragAndDrop();
				} else {
					try {
						dtde.rejectDrop();
					} catch (Exception e) {
						// An exception may be thrown here if the user leaves
						// and enters the window.
						// This is an exceptional case.
					}
					dtde.dropComplete(false);
					TreeDragAndDropWidget.this.resetDragAndDrop();
					return;
				}
			} catch (UnsupportedFlavorException ufe) {
				// So this is not a JVM local object, maybe it's a String...
				try {
					String droppedString = (String) dtde.getTransferable()
							.getTransferData(DataFlavor.stringFlavor);
					if (TreeDragAndDropWidget.this.mayDropHere(droppedString,
							TreeDragAndDropWidget.this.jcomp, dropPath)) {
						dtde.dropComplete(this.dropString(droppedString));
						TreeDragAndDropWidget.this.resetDragAndDrop();
					} else {
						dtde.rejectDrop();
						dtde.dropComplete(false);
						TreeDragAndDropWidget.this.resetDragAndDrop();
						return;
					}
				} catch (Exception exception) {
					dtde.rejectDrop();
					dtde.dropComplete(false);
					TreeDragAndDropWidget.this.resetDragAndDrop();
					return;
				}
			} catch (Exception e) {
				// ClassCastException: So this is a JVM local object but not a
				// TransferableTreeNode, right? Well, then just discard
				// IOException: So there's a problem deserializing this, right?
				// Well, then just discard
				e.printStackTrace();
				dtde.rejectDrop();
				dtde.dropComplete(true);
				TreeDragAndDropWidget.this.resetDragAndDrop();
				return;
			}
		}

		private boolean dropString(String droppedString) {
			// Get the mutable TreeModel
			DefaultTreeModel model = (DefaultTreeModel) TreeDragAndDropWidget.this.jcomp
					.getModel();

			// Ask the listeners to handle this drop
			boolean doItOurselves = true;
			EventListener[] listeners = TreeDragAndDropWidget.this.listeners
					.getListeners(StringTreeDnDListener.class);
			if ((listeners != null) && (listeners.length > 0)) {
				try {
					StringTreeDnDEvent event = new StringTreeDnDEvent(
							droppedString, TreeDragAndDropWidget.this.jcomp,
							TreeDragAndDropWidget.this.dropNode);
					for (int i = 0; i < listeners.length; i++) {
						((StringTreeDnDListener) listeners[i]).drop(event);
					}
				} catch (DnDVetoException exception) {
					doItOurselves = true;
				}
			}
			if (doItOurselves) {
				DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(
						droppedString);

				MutableTreeNode parent = (MutableTreeNode) TreeDragAndDropWidget.this.dropNode
						.getParent();
				if (TreeDragAndDropWidget.this.dropNode.isLeaf()) {
					int index = parent
							.getIndex(TreeDragAndDropWidget.this.dropNode);
					model.insertNodeInto(newNode, parent, index + 1);
				} else {
					model.insertNodeInto(newNode,
							TreeDragAndDropWidget.this.dropNode, 0); // dropNode.getChildCount()
					// );
				}
			}
			return true;
		}

		private boolean dropNodes(JTree aSourceTree,
				MutableTreeNode aSourceNode, JTree aTargetTree,
				MutableTreeNode aDropNode, boolean move) {
			// Get the mutable TreeModel
			DefaultTreeModel sourceModel = (DefaultTreeModel) aSourceTree
					.getModel();
			DefaultTreeModel targetModel = (DefaultTreeModel) aTargetTree
					.getModel();

			boolean doItOurselves = true;
			EventListener[] listeners = TreeDragAndDropWidget.this.listeners
					.getListeners(TreeTreeDnDListener.class);
			if ((listeners != null) && (listeners.length > 0)) {
				try {
					TreeTreeDnDEvent event = new TreeTreeDnDEvent(aSourceTree,
							aSourceNode, aTargetTree, aDropNode);
					for (int i = 0; i < listeners.length; i++) {
						((TreeTreeDnDListener) listeners[i]).drop(event);
					}
				} catch (DnDVetoException exception) {
					doItOurselves = false;
				}
			}
			if (doItOurselves) {
				MutableTreeNode sourceNodeCopy = aSourceNode;
				if (move) {
					sourceModel.removeNodeFromParent(aSourceNode);
				} else {
					sourceNodeCopy = this.recursivelyCopyNodes(targetModel,
							aSourceNode);
				}
				// Attach the draggedNode into the new parent
				MutableTreeNode parent = (MutableTreeNode) aDropNode
						.getParent();
				if (aDropNode.isLeaf() && (parent != null)) {
					int index = parent.getIndex(aDropNode);
					targetModel.insertNodeInto(sourceNodeCopy, parent,
							index + 1);
				} else {
					targetModel.insertNodeInto(sourceNodeCopy, aDropNode, 0);// aDropNode.getChildCount()
					// );
				}
			}
			return true;
		}

		private DefaultMutableTreeNode recursivelyCopyNodes(
				DefaultTreeModel aModel, TreeNode aNode) {
			DefaultMutableTreeNode copy = new DefaultMutableTreeNode(aNode
					.toString());
			copy.setAllowsChildren(aNode.getAllowsChildren());
			if (aNode.getChildCount() != 0) {
				Enumeration children = aNode.children();
				while (children.hasMoreElements()) {
					TreeNode child = (TreeNode) children.nextElement();
					DefaultMutableTreeNode childCopy = this
							.recursivelyCopyNodes(aModel, child);
					aModel
							.insertNodeInto(childCopy, copy, copy
									.getChildCount());
					childCopy.setParent(copy);
				}
			}
			return copy;
		}

		@Override
        public void dragExit(DropTargetEvent dte) {
			TreeDragAndDropWidget.this.dropNode = null;
			TreeDragAndDropWidget.this.rendererProxy.setDropNode(null);
			TreeDragAndDropWidget.this.jcomp.repaint();
		}

		@Override
        public void dropActionChanged(DropTargetDragEvent dtde) {
		}

		private Transferable getTransferable(DropTargetDragEvent dtde) {
			try {
				DropTargetContext context = dtde.getDropTargetContext();
				if (TreeDragAndDropWidget.getTransferableMethod == null) {
					TreeDragAndDropWidget.getTransferableMethod = context
							.getClass().getDeclaredMethod("getTransferable",
									TreeDragAndDropWidget.EMPTY_CLASS_ARRAY);
					TreeDragAndDropWidget.getTransferableMethod
							.setAccessible(true);
				}
				return (Transferable) TreeDragAndDropWidget.getTransferableMethod
						.invoke(context,
								TreeDragAndDropWidget.EMPTY_OBJECT_ARRAY);
			} catch (Exception e) {
				e.printStackTrace(System.err);
				return null;
			}
		}

		/** This node to avoid too many invocations to dragOver */
		private TreeNode lastDragOverNode = null;

		@Override
        public void dragOver(DropTargetDragEvent dtde) {
			if (!TreeDragAndDropWidget.this.jcomp.isEnabled()) {
				dtde.rejectDrag();
				return;
			}

			// Is this a valid node for dropping?
			TreePath dropPath = TreeDragAndDropWidget.this.jcomp
					.getClosestPathForLocation(dtde.getLocation().x, dtde
							.getLocation().y);

			TreeNode currentDropNode = (TreeNode) dropPath
					.getLastPathComponent();

			if ((dropPath == null) || (currentDropNode == null)
					|| currentDropNode.equals(this.lastDragOverNode)) {
				return;
			} else {
				this.lastDragOverNode = currentDropNode;
			}

			Transferable transferable = this.getTransferable(dtde); // dtde.getTransferable();

			boolean mayDropHere = false;

			try {
				// WARNING: getTransferable available only on JDK 1.5
				TransferableTreeNode transferredNode = (TransferableTreeNode) transferable
						.getTransferData(TransferableTreeNode
								.getJavaJVMLocalObjectFlavor());
				JTree sourceTree = transferredNode.getSourceTree();
				MutableTreeNode sourceNode = transferredNode.getSourceNode();
				if (TreeDragAndDropWidget.this.mayDropHere(sourceTree,
						sourceNode, dropPath)) {
					TreeDragAndDropWidget.this.dropNode = (MutableTreeNode) dropPath
							.getLastPathComponent();
					if (!TreeDragAndDropWidget.this.jcomp.isExpanded(dropPath))
						TreeDragAndDropWidget.this.jcomp.expandPath(dropPath);
					mayDropHere = true;
				} else {
					TreeDragAndDropWidget.this.dropNode = null;
				}
			} catch (UnsupportedFlavorException ufe) {
				// Oh, this is not a TransferableTreeNode, so maybe is a String,
				// maybe?
				try {
					String sourceText = (String) transferable
							.getTransferData(DataFlavor.stringFlavor);
					if (TreeDragAndDropWidget.this.mayDropHere(sourceText,
							TreeDragAndDropWidget.this.jcomp, dropPath)) {
						TreeDragAndDropWidget.this.dropNode = (MutableTreeNode) dropPath
								.getLastPathComponent();
						if (!TreeDragAndDropWidget.this.jcomp
								.isExpanded(dropPath))
							TreeDragAndDropWidget.this.jcomp
									.expandPath(dropPath);
						mayDropHere = true;
					} else {
						TreeDragAndDropWidget.this.dropNode = null;
					}
				} catch (Exception e) {
					// Well, whatever, just discard
					TreeDragAndDropWidget.this.dropNode = null;
				}
			} catch (Exception e) {
				// IOException: Oh, there's a problem with serialization. Maybe
				// a classloader issue? Well, ummm... just discard this and say
				// no
				// ClassCastException: Oh, user is transferring a JVM object but
				// not a TransferableTreeNode, well, umm... just discard and say
				// no
				TreeDragAndDropWidget.this.dropNode = null;
			}
			TreeDragAndDropWidget.this.rendererProxy
					.setDropAllowed(mayDropHere);
			TreeDragAndDropWidget.this.rendererProxy
					.setDropNode((TreeNode) dropPath.getLastPathComponent());
			TreeDragAndDropWidget.this.jcomp.repaint();
			if (!mayDropHere) {
				dtde.rejectDrag();
			} else {
				dtde.acceptDrag(dtde.getDropAction());
			}
			TreeDragAndDropWidget.this.jcomp.repaint();
		}

		@Override
        public void dragEnter(DropTargetDragEvent dtde) {
			this.dragOver(dtde);
			// dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE );
		}
	}

	/**
	 * Invoked to decide if a given String can be dropped in the last path
	 * component of the given path.
	 * 
	 * @param aSourceString
	 *            the String being dragged.
	 * @param aPath
	 *            the path to drop into.
	 * @return true to allow the drop operation, false otherwise.
	 */
	private boolean mayDropHere(String aSourceString, JTree aTargetTree,
			TreePath aPath) {
		return this.mayDropHere(aSourceString, aTargetTree, (TreeNode) aPath
				.getLastPathComponent());
	}

	/**
	 * Invoked to decide if a given String can be dropped in the last path
	 * component of the given path.
	 * 
	 * @param aSourceString
	 *            the String being dragged.
	 * @return true to allow the drop operation, false otherwise.
	 */
	private boolean mayDropHere(String aSourceString, JTree aTargetTree,
			TreeNode aNode) {
		EventListener[] listeners = this.listeners
				.getListeners(StringTreeDnDListener.class);
		if ((listeners != null) && (listeners.length > 0)) {
			try {
				StringTreeDnDEvent event = new StringTreeDnDEvent(
						aSourceString, aTargetTree, aNode);
				for (int i = 0; i < listeners.length; i++) {
					((StringTreeDnDListener) listeners[i]).mayDrop(event);
				}
			} catch (DnDVetoException exception) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Invoked to decide if draggedNode can be dropped in the last path
	 * component of the given path.
	 * 
	 * @param aSourceTree
	 *            the source tree.
	 * @param aSourceNode
	 *            the source node.
	 * @param aPath
	 *            the path to drop into.
	 * @return true to allow the drop operation, false otherwise.
	 */
	private boolean mayDropHere(JTree aSourceTree, MutableTreeNode aSourceNode,
			TreePath aPath) {
		if (aPath == null)
			return false;
		return this.mayDropHere(aSourceTree, aSourceNode, (TreeNode) aPath
				.getLastPathComponent());
	}

	/**
	 * Invoked to decide if draggedNode can be dropped into aNode.
	 * 
	 * @param aSourceTree
	 *            the source tree.
	 * @param aSourceNode
	 *            the source node.
	 * @param aNode
	 *            the node to drop into.
	 * @return true to allow the drop operation, false to avoid it.
	 */
	private boolean mayDropHere(JTree aSourceTree, MutableTreeNode aSourceNode,
			TreeNode aNode) {
		boolean mayDropHere = (aNode != aSourceNode)
				&& (aNode instanceof MutableTreeNode)
				&& ((aNode.getParent() == null) || (aNode.getParent() instanceof MutableTreeNode))
				&& (this.jcomp.getModel() instanceof DefaultTreeModel)
				&& !((this.jcomp == aSourceTree) && TreeDragAndDropWidget
						.isAncestorOf(aSourceNode, aNode));

		if (mayDropHere) {
			// Ask listeners
			EventListener[] listeners = this.listeners
					.getListeners(TreeTreeDnDListener.class);
			if ((listeners != null) && (listeners.length > 0)) {
				try {
					TreeTreeDnDEvent event = new TreeTreeDnDEvent(aSourceTree,
							aSourceNode, this.jcomp, aNode);
					for (int i = 0; i < listeners.length; i++) {
						((TreeTreeDnDListener) listeners[i]).mayDrop(event);
					}
				} catch (DnDVetoException exception) {
					mayDropHere = false;
				}
			}
		}

		return mayDropHere;
	}

	/**
	 * See if aPossibleParent is ancestor of aNode
	 */
	private static boolean isAncestorOf(TreeNode aPossibleParent, TreeNode aNode) {
		if ((aPossibleParent == null) || (aNode.getParent() == null))
			return false;
		else if (aNode.getParent() == aPossibleParent)
			return true;
		else
			return TreeDragAndDropWidget.isAncestorOf(aPossibleParent, aNode
					.getParent());
	}

	private void resetDragAndDrop() {
		this.dropNode = null;
		this.rendererProxy.setDraggedNode(null);
		this.rendererProxy.setDropAllowed(false);
		this.rendererProxy.setDropNode(null);
		this.jcomp.repaint();
	}
}
