/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */
package org.openide.explorer.view;

import org.openide.ErrorManager;
import org.openide.nodes.Children;
import org.openide.nodes.Index;
import org.openide.nodes.Node;
import org.openide.util.datatransfer.PasteType;

import java.awt.Component;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Line2D.Double;
import java.util.ArrayList;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreePath;


/** Implementation of drop support for asociated Tree View.
*
* @author Dafe Simonek, Jiri Rechtacek
*/
final class TreeViewDropSupport implements DropTargetListener, Runnable {
    final static protected int FUSSY_POINTING = 3;
    final static private int DELAY_TIME_FOR_EXPAND = 1000;
    final static private int SHIFT_DOWN = -1;
    final static private int SHIFT_RIGHT = 10;
    final static private int SHIFT_LEFT = 15;

    // Attributes

    /** true if support is active, false otherwise */
    boolean active = false;
    boolean dropTargetPopupAllowed;

    /** Drop target asociated with the tree */
    DropTarget dropTarget;

    /** Node area which we were during
    * DnD operation. */
    Rectangle lastNodeArea;
    private int upperNodeIdx = -1;
    private int lowerNodeIdx = -1;
    private int dropIndex = -1;

    /** Swing Timer for expand node's parent with delay time. */
    Timer timer;

    /** Glass pane for JTree which is associate with this class. */
    DropGlassPane dropPane;
    private int pointAt = DragDropUtilities.NODE_CENTRAL;

    // Associations

    /** View manager. */
    protected TreeView view;

    /** The component we are supporting with drop support */
    protected JTree tree;

    // Operations

    /** Creates new TreeViewDropSupport */
    public TreeViewDropSupport(TreeView view, JTree tree, boolean dropTargetPopupAllowed) {
        this.view = view;
        this.tree = tree;
        this.dropTargetPopupAllowed = dropTargetPopupAllowed;
    }

    public void setDropTargetPopupAllowed(boolean value) {
        dropTargetPopupAllowed = value;
    }

    public boolean isDropTargetPopupAllowed() {
        return dropTargetPopupAllowed;
    }

    /** User is starting to drag over us */
    public void dragEnter(DropTargetDragEvent dtde) {
        checkStoredGlassPane();

        dropIndex = -1;
        
        // set a status and cursor of dnd action
        doDragOver(dtde);
    }

    /** User drags over us */
    public void dragOver(DropTargetDragEvent dtde) {
        // bugfix #34483; jdk1.4.1 on w2k could calls dragOver() before dragEnter()
        // (jkdbug fixed in 1.4.2)
        // this check make dragOver/Enter more robust
        checkStoredGlassPane();

        // set a status and cursor of dnd action
        doDragOver(dtde);
    }

    private void checkStoredGlassPane() {
        // remember current glass pane to set back at end of dragging over this compoment
        if (!DropGlassPane.isOriginalPaneStored()) {
            Component comp = tree.getRootPane().getGlassPane();
            DropGlassPane.setOriginalPane(tree, comp, comp.isVisible());

            // set glass pane for paint selection line
            dropPane = DropGlassPane.getDefault(tree);
            tree.getRootPane().setGlassPane(dropPane);
            dropPane.revalidate();
            dropPane.setVisible(true);
        }
    }

    /** Process events dragEnter or dragOver. */
    private void doDragOver(DropTargetDragEvent dtde) {
        int dropAction = dtde.getDropAction();
        int allowedDropActions = view.getAllowedDropActions();
        ExplorerDnDManager.getDefault().prepareCursor(
            DragDropUtilities.chooseCursor(
                dtde.getDropTargetContext().getComponent(), dropAction, (dropAction & view.getAllowedDropActions()) != 0
            )
        );
        dropAction = ExplorerDnDManager.getDefault().getAdjustedDropAction(
                dropAction, allowedDropActions);

        // 1. test if I'm over any node
        TreePath tp = getTreePath(dtde, dropAction);
        Node dropNode;
        
        // 2. find node for drop
        Point p = dtde.getLocation();
        if (tp == null) {
            // #64469: Can't drop into empty explorer area
            dropNode = view.manager.getRootContext ();
            if (canDrop(dropNode, dropAction)) {
                // ok, root accept
                dtde.acceptDrag(dropAction);
            } else {
                dtde.rejectDrag();
            }
            return ;
        } else {
            dropNode = getNodeForDrop(p);
        }

        // if I haven't any node for drop then reject drop
        if (dropNode == null) {
            dropIndex = -1;
            dtde.rejectDrag();
            removeDropLine();

            return;
        }

        Rectangle nodeArea = tree.getPathBounds(tp);
        int endPointX = nodeArea.x + nodeArea.width;
        int row = tree.getRowForPath(tp);

        if (nodeArea != null) {
            pointAt = DragDropUtilities.NODE_CENTRAL;

            if (p.y <= (nodeArea.y + FUSSY_POINTING)) {
                // don't get line above root
                if (row != 0) {
                    // point above node
                    pointAt = DragDropUtilities.NODE_UP;

                    TreePath upPath = tree.getPathForRow(row - 1);

                    if ((upPath != null) && !upPath.equals(tp)) {
                        endPointX = Math.max(
                                nodeArea.x + nodeArea.width,
                                tree.getPathBounds(upPath).x + tree.getPathBounds(upPath).width
                            );
                    }

                    // drop candidate is parent
                    if (dropNode.getParentNode() != null) {
                        dropNode = dropNode.getParentNode();
                        tp = null;
                    }
                }
            } else if (p.y >= ((nodeArea.y + nodeArea.height) - FUSSY_POINTING)) {
                // exclude expanded folder
                if (!view.isExpanded(dropNode)) {
                    // point bellow node
                    pointAt = DragDropUtilities.NODE_DOWN;

                    TreePath downPath = tree.getPathForRow(row + 1);

                    if ((downPath != null) && !downPath.equals(tp)) {
                        endPointX = Math.max(
                                nodeArea.x + nodeArea.width,
                                tree.getPathBounds(downPath).x + tree.getPathBounds(downPath).width
                            );
                    }

                    // drop candidate is parent
                    if (dropNode.getParentNode() != null) {
                        dropNode = dropNode.getParentNode();
                        tp = null;
                    }
                }
            }
        }

        endPointX = endPointX + SHIFT_RIGHT;

        // 2.b. check index cookie
        Index indexCookie = (Index) dropNode.getCookie(Index.class);

        if (indexCookie != null) {
            if (pointAt == DragDropUtilities.NODE_UP) {
                lowerNodeIdx = indexCookie.indexOf(getNodeForDrop(p));
                upperNodeIdx = lowerNodeIdx - 1;
            } else if (pointAt == DragDropUtilities.NODE_DOWN) {
                upperNodeIdx = indexCookie.indexOf(getNodeForDrop(p));
                lowerNodeIdx = upperNodeIdx + 1;
            }
            dropIndex = lowerNodeIdx;
        }
        if( dropNode == getNodeForDrop(p) )
            dropIndex = -1;

        // 3. expand with a delay
        if (
            ((timer == null) || !timer.isRunning()) && (dropNode != null) && !dropNode.isLeaf() &&
                !view.isExpanded(dropNode)
        ) {
            // ok, let's expand in a while
            // node is candidate for expand
            final Node cn = dropNode;

            // remove old timer
            removeTimer();

            // create new timer
            timer = new Timer(
                    DELAY_TIME_FOR_EXPAND,
                    new ActionListener() {
                        final public void actionPerformed(ActionEvent e) {
                            view.expandNode(cn);
                        }
                    }
                );
            timer.setRepeats(false);
            timer.start();
        }

        // 4. present node for drop
        // prepare selection or line
        if (pointAt == DragDropUtilities.NODE_CENTRAL) {
            // no line
            dropPane.setDropLine(null);
        } else {
            // line and selection of parent if any
            if (pointAt == DragDropUtilities.NODE_UP) {
                Line2D line = new Double(
                        nodeArea.x - SHIFT_LEFT, nodeArea.y + SHIFT_DOWN, endPointX, nodeArea.y + SHIFT_DOWN
                    );
                convertBoundsAndSetDropLine(line);

                // enlagre node area with area for line
                Rectangle lineArea = new Rectangle(
                        nodeArea.x - SHIFT_LEFT, (nodeArea.y + SHIFT_DOWN) - 3, endPointX - nodeArea.x + SHIFT_LEFT, 5
                    );
                nodeArea = (Rectangle) nodeArea.createUnion(lineArea);
            } else {
                Line2D line = new Double(
                        nodeArea.x - SHIFT_LEFT, nodeArea.y + nodeArea.height + SHIFT_DOWN, endPointX,
                        nodeArea.y + nodeArea.height + SHIFT_DOWN
                    );
                convertBoundsAndSetDropLine(line);

                // enlagre node area with area for line
                Rectangle lineArea = new Rectangle(
                        nodeArea.x - SHIFT_LEFT, nodeArea.y + nodeArea.height, endPointX - nodeArea.x + SHIFT_LEFT,
                        SHIFT_DOWN + 3
                    );
                nodeArea = (Rectangle) nodeArea.createUnion(lineArea);
            }

            // the parent node won't be selected

            /*// select parent and enlarge paint area
            if (tp.getParentPath ()!=null) {
                tp = tp.getParentPath ();
            }
            nodeArea = (Rectangle)nodeArea.createUnion (tree.getPathBounds (tp));*/
        }

        // back normal view w/o any selecetion nor line
        if ((lastNodeArea != null) && (!lastNodeArea.equals(nodeArea))) {
            NodeRenderer.dragExit();
            repaint(lastNodeArea);
        }

        // paint new state
        if (!nodeArea.equals(lastNodeArea)) {
            if (tp != null) {
                NodeRenderer.dragEnter(tp.getLastPathComponent());
            }

            repaint(nodeArea);
            lastNodeArea = nodeArea;
            removeTimer();
        }

        // 5. show to cursor belong to state
        if (canDrop(dropNode, dropAction)) {
            // ok, can accept
            dtde.acceptDrag(dropAction);
        } else {
            // can only reorder?
            if (canReorder(dropNode, ExplorerDnDManager.getDefault().getDraggedNodes())) {
                // ok, can accept only reoder
                dtde.acceptDrag(dropAction);
            } else {
                dtde.rejectDrag();
            }
        }
    }

    /** Repaints TreeView, the given rectangle is enlarged for 5 pixels
     * because some parts was not repainted correctly.
     * @param Rectangle r rectangle which will be repainted.*/
    private void repaint(Rectangle r) {
        tree.repaint(r.x - 5, r.y - 5, r.width + 10, r.height + 10);
    }

    /** Converts line's bounds by the bounds of the root pane. Drop glass pane
     * is over this root pane. After covert a given line is set to drop glass pane.
     * @param line line for show in drop glass pane */
    private void convertBoundsAndSetDropLine(final Line2D line) {
        int x1 = (int) line.getX1();
        int x2 = (int) line.getX2();
        int y1 = (int) line.getY1();
        int y2 = (int) line.getY2();
        Point p1 = SwingUtilities.convertPoint(tree, x1, y1, tree.getRootPane());
        Point p2 = SwingUtilities.convertPoint(tree, x2, y2, tree.getRootPane());
        line.setLine(p1, p2);
        dropPane.setDropLine(line);
    }

    /** Removes timer and all listeners. */
    private void removeTimer() {
        if (timer != null) {
            ActionListener[] l = (ActionListener[]) timer.getListeners(ActionListener.class);

            for (int i = 0; i < l.length; i++) {
                timer.removeActionListener(l[i]);
            }

            timer.stop();
            timer = null;
        }
    }

    public void dropActionChanged(DropTargetDragEvent dtde) {
        // check if the nodes are willing to do selected action
        Node[] nodes = ExplorerDnDManager.getDefault().getDraggedNodes();
        int dropAction = ExplorerDnDManager.getDefault().getAdjustedDropAction(
                dtde.getDropAction(), view.getAllowedDropActions()
            );

        for (int i = 0; i < nodes.length; i++) {
            if (
                ((view.getAllowedDropActions() & dropAction) == 0) ||
                    !DragDropUtilities.checkNodeForAction(nodes[i], dropAction)
            ) {
                // this action is not supported
                dtde.rejectDrag();

                return;
            }
        }

        return;
    }

    /** User exits the dragging */
    public void dragExit(DropTargetEvent dte) {
        dropIndex = -1;
        
        stopDragging();
    }

    private void removeDropLine() {
        dropPane.setDropLine(null);

        if (lastNodeArea != null) {
            NodeRenderer.dragExit();
            repaint(lastNodeArea);
            lastNodeArea = null;
        }
    }

    private void stopDragging() {
        removeDropLine();
        removeTimer();

        // set back the remembered glass pane
        if (DropGlassPane.isOriginalPaneStored()) {
            DropGlassPane.putBackOriginal();
        }
    }

    /** Get a node on given point or null if there none*/
    private Node getNodeForDrop(Point p) {
        if (p != null) {
            TreePath tp = tree.getPathForLocation(p.x, p.y);

            if (tp != null) {
                return DragDropUtilities.secureFindNode(tp.getLastPathComponent());
            }
        }

        return null;
    }

    private boolean canReorder(Node folder, Node[] dragNodes) {
        if ((ExplorerDnDManager.getDefault().getNodeAllowedActions() & DnDConstants.ACTION_MOVE) == 0) {
            return false;
        }

        if ((folder == null) || (dragNodes.length == 0)) {
            return false;
        }

        // has folder a index cookie?
        Index ic = (Index) folder.getCookie(Index.class);

        if (ic == null) {
            return false;
        }

        // folder has index cookie
        // check if all dragNodes are from same folder
        for (int i = 0; i < dragNodes.length; i++) {
            // bugfix #23988, check if dragNodes[i] isn't null
            if (dragNodes[i] == null) {
                return false;
            }

            if (dragNodes[i].getParentNode() == null) {
                return false;
            }

            if (!dragNodes[i].getParentNode().equals(folder)) {
                return false;
            }
        }

        return true;
    }

    private void performReorder(final Node folder, Node[] dragNodes, int lNode, int uNode) {
        try {
            Index indexCookie = (Index) folder.getCookie(Index.class);

            if (indexCookie != null) {
                int[] perm = new int[indexCookie.getNodesCount()];
                int[] indexes = new int[dragNodes.length];
                int indexesLength = 0;

                for (int i = 0; i < dragNodes.length; i++) {
                    int idx = indexCookie.indexOf(dragNodes[i]);

                    if ((idx >= 0) && (idx < perm.length)) {
                        indexes[indexesLength++] = idx;
                    }
                }

                // XXX: normally indexes of dragged nodes should be in ascending order, but
                // it seems that Tree.getSelectionPaths doesn't keep this order
                Arrays.sort(indexes);

                if ((lNode < 0) || (uNode >= perm.length) || (indexesLength == 0)) {
                    return;
                }

                int k = 0;

                for (int i = 0; i < perm.length; i++) {
                    if (i <= uNode) {
                        if (!containsNumber(indexes, indexesLength, i)) {
                            perm[i] = k++;
                        }

                        if (i == uNode) {
                            for (int j = 0; j < indexesLength; j++) {
                                if (indexes[j] <= uNode) {
                                    perm[indexes[j]] = k++;
                                }
                            }
                        }
                    } else {
                        if (i == lNode) {
                            for (int j = 0; j < indexesLength; j++) {
                                if (indexes[j] >= lNode) {
                                    perm[indexes[j]] = k++;
                                }
                            }
                        }

                        if (!containsNumber(indexes, indexesLength, i)) {
                            perm[i] = k++;
                        }
                    }
                }

                // check for identity permutation
                for (int i = 0; i < perm.length; i++) {
                    if (perm[i] != i) {
                        indexCookie.reorder(perm);

                        break;
                    }
                }
            }
        } catch (Exception e) {
            // Pending: add annotation or remove try/catch block
            ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
        }
    }

    private boolean containsNumber(int[] arr, int arrLength, int n) {
        for (int i = 0; i < arrLength; i++) {
            if (arr[i] == n) {
                return true;
            }
        }

        return false;
    }

    private Node[] findDropedNodes(Node folder, Node[] dragNodes) {
        if ((folder == null) || (dragNodes.length == 0)) {
            return null;
        }

        Node[] dropNodes = new Node[dragNodes.length];
        Children children = folder.getChildren();

        for (int i = 0; i < dragNodes.length; i++) {
            dropNodes[i] = children.findChild(dragNodes[i].getName());
        }

        return dropNodes;
    }

    /** Can node recieve given drop action? */

    // XXX canditate for more general support
    private boolean canDrop(Node n, int dropAction) {
        if (n == null) {
            return false;
        }

        // Test to see if the target node supports the drop action
        if ((view.getAllowedDropActions() & dropAction) == 0) {
            return false;
        }

        // test if a parent of the dragged nodes isn't the node over
        // only for MOVE action
        if ((DnDConstants.ACTION_MOVE & dropAction) != 0) {
            Node[] nodes = ExplorerDnDManager.getDefault().getDraggedNodes();

            if (nodes == null) {
                return false;
            }

            for (int i = 0; i < nodes.length; i++) {
                if (n.equals(nodes[i].getParentNode())) {
                    return false;
                }
            }
        }

        Transferable trans = ExplorerDnDManager.getDefault().getDraggedTransferable(
                (DnDConstants.ACTION_MOVE & dropAction) != 0
            );

        if (trans == null) {
            return false;
        }

        // get paste types for given transferred transferable
        PasteType pt = DragDropUtilities.getDropType(n, trans, dropAction, dropIndex);

        return (pt != null);
    }

    /** Performs the drop action, if we are dropping on
    * right node and target node agrees.
    */
    public void drop(DropTargetDropEvent dtde) {
        stopDragging();

        // find node for the drop perform
        Node dropNode = getNodeForDrop(dtde.getLocation());
        
        // #64469: Can't drop into empty explorer area
        if (dropNode == null) {
            dropNode = view.manager.getRootContext ();
        } else if (pointAt != DragDropUtilities.NODE_CENTRAL) {
            dropNode = dropNode.getParentNode();
        }

        Node[] dragNodes = ExplorerDnDManager.getDefault().getDraggedNodes();
        int dropAction = ExplorerDnDManager.getDefault().getAdjustedDropAction(
                dtde.getDropAction(), view.getAllowedDropActions()
            );

        if (!canDrop(dropNode, dropAction)) {
            if (canReorder(dropNode, dragNodes)) {
                performReorder(dropNode, dragNodes, lowerNodeIdx, upperNodeIdx);
                dtde.acceptDrop(dropAction);
            } else {
                dtde.rejectDrop();
            }

            dtde.dropComplete(true);

            return;
        }

        // finally perform the drop
        dtde.acceptDrop(dropAction);

        if (DnDConstants.ACTION_LINK == dropAction) {
            // construct all paste types
            PasteType[] ptCut = new PasteType[] {  };

            // construct all paste types
            PasteType[] ptCopy = new PasteType[] {  };

            // do not try get paste types for move if MOVE is not allowed
            if ((ExplorerDnDManager.getDefault().getNodeAllowedActions() & DnDConstants.ACTION_MOVE) != 0) {
                ptCut = DragDropUtilities.getPasteTypes(
                        dropNode, ExplorerDnDManager.getDefault().getDraggedTransferable(true)
                    );
            }

            // do not try get paste types for copy if COPY is not allowed
            if ((ExplorerDnDManager.getDefault().getNodeAllowedActions() & DnDConstants.ACTION_COPY) != 0) {
                ptCopy = DragDropUtilities.getPasteTypes(
                        dropNode, ExplorerDnDManager.getDefault().getDraggedTransferable(false)
                    );
            }

            TreeSet setPasteTypes = new TreeSet(
                    new Comparator() {
                        public int compare(Object obj1, Object obj2) {
                            return ((PasteType) obj1).getName().compareTo(((PasteType) obj2).getName());

                            // have to fix: the different actions can have same name!!!

                            /*int res = ((PasteType)obj1).getName ().compareTo (((PasteType)obj2).getName ());
                            System.out.println("res1: "+res);
                            if (res == 0) {
                                res = System.identityHashCode(obj1)-System.identityHashCode(obj2);
                            }
                            System.out.println("res2: "+res);
                            return res;*/
                        }
                    }
                );

            for (int i = 0; i < ptCut.length; i++) {
                //System.out.println(ptCut[i].getName()+", "+System.identityHashCode(ptCut[i]));
                setPasteTypes.add(ptCut[i]);
            }

            for (int i = 0; i < ptCopy.length; i++) {
                //System.out.println(ptCopy[i].getName()+", "+System.identityHashCode(ptCopy[i]));
                setPasteTypes.add(ptCopy[i]);
            }

            DragDropUtilities.createDropFinishPopup(setPasteTypes).show(
                tree, Math.max(dtde.getLocation().x - 5, 0), Math.max(dtde.getLocation().y - 5, 0)
            );

            // reorder have to be perform
            if (canReorder(dropNode, dragNodes)) {
                final Node tempDropNode = dropNode;
                final int tmpUpper = upperNodeIdx;
                final int tmpLower = lowerNodeIdx;
                final Node[] tempDragNodes = dragNodes;
                DragDropUtilities.setPostDropRun(
                    new Runnable() {
                        public void run() {
                            performReorder(
                                tempDropNode, findDropedNodes(tempDropNode, tempDragNodes), tmpLower, tmpUpper
                            );
                        }
                    }
                );
            }
        } else {
            // get correct paste type
            PasteType pt = DragDropUtilities.getDropType(
                    dropNode,
                    ExplorerDnDManager.getDefault().getDraggedTransferable(
                        (DnDConstants.ACTION_MOVE & dropAction) != 0
                    ), dropAction,
                    dropIndex
                );

            //remember the Nodes before the drop
            final Node[] preNodes = dropNode.getChildren().getNodes( true );
            final Node parentNode = dropNode;

            Node[] diffNodes = DragDropUtilities.performPaste(pt, dropNode);
            ExplorerDnDManager.getDefault().setDraggedNodes(diffNodes);

            //postpone the potential re-order so that the drop Node has enough 
            //time to re-create its children
            SwingUtilities.invokeLater( new Runnable() {
                public void run() {
                    Node[] diffNodes = getDiffNodes( parentNode, preNodes );
                    if( canReorder( parentNode, diffNodes ) ) {
                        performReorder( parentNode, diffNodes, lowerNodeIdx, upperNodeIdx );
                    }
                }
            });
        }

        TreeCellEditor tce = tree.getCellEditor();

        if (tce instanceof TreeViewCellEditor) {
            ((TreeViewCellEditor) tce).setDnDActive(false);
        }

        // finished
        dtde.dropComplete(true);
    }

    private Node[] getDiffNodes( Node parent, Node[] childrenBefore ) {
        Node[] childrenCurrent = parent.getChildren().getNodes(true);

        // calculate new nodes
        List pre = Arrays.asList(childrenBefore);
        List post = Arrays.asList(childrenCurrent);
        Iterator it = post.iterator();
        List diff = new ArrayList();

        while (it.hasNext()) {
            Node n = (Node)it.next();

            if (!pre.contains(n)) {
                diff.add(n);
            }
        }

        return (Node[])diff.toArray(new Node[diff.size()]);
    }
    
    /** Activates or deactivates Drag support on asociated JTree
    * component
    * @param active true if the support should be active, false
    * otherwise
    */
    public void activate(boolean active) {
        if (this.active == active) {
            return;
        }

        this.active = active;
        getDropTarget().setActive(active);
    }

    /** Implementation of the runnable interface.
    * Notifies user in AWT thread. */
    public void run() {
        if (!SwingUtilities.isEventDispatchThread()) {
            SwingUtilities.invokeLater(this);

            return;
        }

        DragDropUtilities.dropNotSuccesfull();
    }

    /** @return The tree path to the node the cursor is above now or
    * null if no such node currently exists or if conditions were not
    * satisfied to continue with DnD operation.
    */
    TreePath getTreePath(DropTargetDragEvent dtde, int dropAction) {
        // check location
        Point location = dtde.getLocation();
        TreePath tp = tree.getPathForLocation(location.x, location.y);

        return ((tp != null) && (DragDropUtilities.secureFindNode(tp.getLastPathComponent()) != null)) ? tp : null;
    }

    /** Safe accessor to the drop target which is asociated
    * with the tree */
    DropTarget getDropTarget() {
        if (dropTarget == null) {
            dropTarget = new DropTarget(tree, view.getAllowedDropActions(), this, false);
        }

        return dropTarget;
    }
}
 /* end class TreeViewDropSupport */
