Java D&;D和CCP用于定制TransferHandler

Java D&;D和CCP用于定制TransferHandler,java,swing,drag-and-drop,copy-paste,Java,Swing,Drag And Drop,Copy Paste,我无法为使用自定义TreeModel的JTree实现自定义TransferHandler。 问题来自于我用来管理数据的特定TreeModel 据我所知,在swing中拖放的工作方式如下: 用户开始拖动,数据通过传输处理程序从模型中获取 用户在容器上丢弃数据 在传输处理程序上调用importData(数据应添加到此处的模型) 在传输处理程序上调用exportDone(此处应从模型中删除数据) 这对我来说是个大问题,因为我的模型不能包含任何数据两次。我需要的是: 从旧位置的模型中删除数据 在新位置向

我无法为使用自定义
TreeModel
JTree
实现自定义
TransferHandler
。 问题来自于我用来管理数据的特定
TreeModel

据我所知,在swing中拖放的工作方式如下:

  • 用户开始拖动,数据通过传输处理程序从模型中获取
  • 用户在容器上丢弃数据
  • 在传输处理程序上调用importData(数据应添加到此处的模型)
  • 在传输处理程序上调用exportDone(此处应从模型中删除数据)
  • 这对我来说是个大问题,因为我的模型不能包含任何数据两次。我需要的是:

  • 从旧位置的模型中删除数据
  • 在新位置向模型添加数据
  • 我在谷歌上搜索了一下,发现的唯一一件事就是一个小小的黑客行为,基本上是滥用importData方法,先删除数据,然后忽略exportDone方法

    这适用于拖放,但会破坏CCP功能。 CCP被破坏,因为在
    exportDone
    方法中,我无法确定导出是拖放还是剪切。如果是切割,我需要从模型中删除数据,但如果是跌落,则不需要删除数据

    此外,在使用Copy-and-Cut的
    importData
    方法时,我还有另一个问题。在复制的情况下,我需要克隆我的数据,但当它是一个剪切,我不需要克隆,我实际上更希望如果我不这样做,以保留旧的引用。 但是
    importData
    方法中给出的唯一参数是
    TransferSupport
    对象。
    TransferSupport
    无法告诉您该操作是复制还是剪切操作

    这是代码,如果它有任何帮助:(它已经很大了,对不起)

    package-pkg;
    导入java.awt.KeyboardFocusManager;
    导入java.awt.event.ActionEvent;
    导入java.awt.event.ActionListener;
    导入java.beans.PropertyChangeEvent;
    导入java.beans.PropertyChangeListener;
    导入javax.swing.Action;
    导入javax.swing.JComponent;
    公共类TransferActionListener实现ActionListener、PropertyChangeListener{
    私有JComponent focusOwner=null;
    /*
    *该类来自oracle教程网站,用于支持复制剪切粘贴。
    * http://docs.oracle.com/javase/tutorial/uiswing/dnd/listpaste.html
    */
    公共静态最终TransferActionListener实例=新TransferActionListener();
    私有TransferActionListener(){
    KeyboardFocusManager=KeyboardFocusManager.getCurrentKeyboardFocusManager();
    manager.addPropertyChangeListener(“permanentFocusOwner”,this);
    }
    公共无效属性更改(属性更改事件e){
    Object obj=e.getNewValue();
    if(JComponent的obj实例){
    focusOwner=(JComponent)obj;
    }否则{
    focusOwner=null;
    }
    }
    已执行的公共无效操作(操作事件e){
    if(focusOwner==null){
    返回;
    }
    字符串操作=(字符串)e.getActionCommand();
    动作a=focusOwner.getActionMap().get(动作);
    如果(a!=null){
    a、 actionPerformed(新的ActionEvent(focusOwner,ActionEvent.ACTION_-PERFORMED,null));
    }
    }
    }
    包装;
    导入java.awt.datatransfer.DataFlavor;
    导入java.awt.datatransfer.transfer;
    导入java.awt.datatransfer.UnsupportedFlavorException;
    导入java.io.IOException;
    可转移的公共类对象{
    /*
    *此类可用于传输任何类型的java类。
    *只能在同一JVM中使用。
    */
    私人最终数据风味[]口味;
    私人终审法院;
    public ObjectTransferable(E object)抛出ClassNotFoundException{
    obj=对象;
    口味=新的DataFlavor[]{
    新的DataFlavor(DataFlavor.javaJVMLocalObjectMimeType+“class=“+object.getClass().getName())
    };
    }
    公共对象(E对象,DataFlavor){
    obj=对象;
    口味=新的DataFlavor[]{
    风味
    };
    }
    公共对象getTransferData(DataFlavor flavor)引发不受支持的LavorException、IOException{
    如果(!isDataFlavorSupported(flavor)){
    抛出新的不受支持的LavorException(flavor);
    }
    返回obj;
    }
    公共DataFlavor[]getTransferDataFlavors(){
    回归口味;
    }
    支持公共布尔值IsDataFlavor(DataFlavor){
    返回味道[0]。等于(味道);
    }
    }
    包装;
    导入java.util.ArrayList;
    导入java.util.Collections;
    导入java.util.List;
    公共类节点{
    私有最终字符串名;
    私人最终名单儿童;
    私有MyModel模型;
    私有节点父节点;
    公共节点(字符串名称){
    this.name=名称;
    children=newarraylist();
    parent=null;
    }
    受保护的void setModel(MyModel){
    this.model=模型;
    对于(节点子节点:getChildren()){
    setModel(model);
    }
    }
    受保护的MyModel getModel(){
    收益模型;
    }
    受保护的void setParent(节点){
    父节点=节点;
    }
    公共节点getParent(){
    返回父母;
    }
    公共void addChild(节点子节点){
    addChild(child,getChildren().size());
    }
    public void addChild(节点子节点,int索引){
    if(child.getParent()==此){
    抛出新的IllegalArgumentException(“节点“'+child+””已经是“'+this+””的子节点);
    }
    if(child.getParent()!=null){
    抛出新的IllegalArgumentException(“节点“+”子节点“+”已具有父节点”);
    }
    child.setParent(this);
    setModel(getModel());
    添加(索引,子项);
    fireInsertEvent(子级,索引);
    }
    public void removeChild(节点子节点){
    if(child.getParent()!
    
    package pkg;
    
    import java.awt.KeyboardFocusManager;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    
    import javax.swing.Action;
    import javax.swing.JComponent;
    
    public class TransferActionListener implements ActionListener, PropertyChangeListener {
        private JComponent focusOwner = null;
    
        /*
         * This class is taken from the oracle tutorial website for Copy-Cut-Paste support.
         * http://docs.oracle.com/javase/tutorial/uiswing/dnd/listpaste.html
         */
    
        public static final TransferActionListener INSTANCE = new TransferActionListener();
    
        private TransferActionListener() {
            KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
            manager.addPropertyChangeListener("permanentFocusOwner", this);
        }
    
        public void propertyChange(PropertyChangeEvent e) {
            Object obj = e.getNewValue();
            if (obj instanceof JComponent) {
                focusOwner = (JComponent)obj;
            } else {
                focusOwner = null;
            }
        }
    
        public void actionPerformed(ActionEvent e) {
            if (focusOwner == null) {
                return;
            }
    
            String action = (String) e.getActionCommand();
            Action a = focusOwner.getActionMap().get(action);
            if (a != null) {
                a.actionPerformed(new ActionEvent(focusOwner, ActionEvent.ACTION_PERFORMED, null));
            }
        }
    }
    
    package pkg;
    
    import java.awt.datatransfer.DataFlavor;
    import java.awt.datatransfer.Transferable;
    import java.awt.datatransfer.UnsupportedFlavorException;
    import java.io.IOException;
    
    public class ObjectTransferable<E> implements Transferable {
    
        /*
         * This class can be used to transfer any kind of java class.
         * Can only be used within the same JVM.
         */
    
        private final DataFlavor[] flavors;
        private final E obj;
    
        public ObjectTransferable(E object) throws ClassNotFoundException {
            obj = object;
            flavors = new DataFlavor[] {
                new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class="+object.getClass().getName())
            };
        }
    
        public ObjectTransferable(E object, DataFlavor flavor) {
            obj = object;
            flavors = new DataFlavor[] {
                flavor
            };
        }
    
        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
            if (!isDataFlavorSupported(flavor)) {
                throw new UnsupportedFlavorException(flavor);
            }
            return obj;
        }
    
        public DataFlavor[] getTransferDataFlavors() {
            return flavors;
        }
    
        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return flavors[0].equals(flavor);
        }
    
    }
    
    package pkg;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class Node {
    
        private final String name;
        private final List<Node> children;
        private MyModel model;
        private Node parent;
    
        public Node(String name) {
            this.name = name;
            children = new ArrayList<>();
            parent = null;
        }
    
        protected void setModel(MyModel model) {
            this.model = model;
            for (Node child : getChildren()) {
                child.setModel(model);
            }
        }
    
        protected MyModel getModel() {
            return model;
        }
    
        protected void setParent(Node node) {
            parent = node;
        }
    
        public Node getParent() {
            return parent;
        }
    
        public void addChild(Node child) {
            addChild(child, getChildren().size());
        }
    
        public void addChild(Node child, int index) {
            if (child.getParent() == this) {
                throw new IllegalArgumentException("Node '"+child+"' is already a child of '"+this+"'.");
            }
            if (child.getParent() != null) {
                throw new IllegalArgumentException("Node '"+child+"' already has a parent.");
            }
            child.setParent(this);
            child.setModel(getModel());
            children.add(index, child);
            fireInsertEvent(child, index);
        }
    
        public void removeChild(Node child) {
            if (child.getParent() != this) {
                throw new IllegalArgumentException("Node '"+child+"' is not a child of '"+this+"'.");
            }
            int index = children.indexOf(child);
            fireRemoveEvent(child, index);
            child.setParent(null);
            child.setModel(null);
            children.remove(index);
        }
    
        public List<Node> getChildren() {
            return Collections.unmodifiableList(children);
        }
    
        protected void fireInsertEvent(Node node, int index) {
            if (getModel() != null) {
                getModel().fireInsertEvent(node, index);
            }
        }
    
        protected void fireRemoveEvent(Node node, int index) {
            if (getModel() != null) {
                getModel().fireRemoveEvent(node, index);
            }
        }
    
        public String toString() {
            return name;
        }
    
    }
    
    package pkg;
    
    import java.util.ArrayList;
    import java.util.Deque;
    import java.util.LinkedList;
    import java.util.List;
    
    import javax.swing.event.TreeModelEvent;
    import javax.swing.event.TreeModelListener;
    import javax.swing.tree.TreeModel;
    import javax.swing.tree.TreePath;
    
    public class MyModel implements TreeModel {
    
        private final List<TreeModelListener> listeners;
        private Node root;
    
        public MyModel(Node rootNode) {
            listeners = new ArrayList<>();
            root = rootNode;
            root.setModel(this);
        }
    
        public Object getRoot() {
            return root;
        }
    
        /**
         * Returns the parent node for the given child.
         * Assumes that the child is an object of type Node.
         * @param child
         * @return
         */
        public Object getParent(Object child) {
            Node childNode = (Node) child;
            return childNode.getParent();
        }
    
        /**
         * Returns the child node at index for the given parent.
         * Assumes that the parent is an object of type Node.
         * @param child
         * @return
         */
        public Object getChild(Object parent, int index) {
            Node parentNode = (Node) parent;
            return parentNode.getChildren().get(index);
        }
    
        /**
         * Returns the number of children the parent has.
         * Assumes that the parent is an object of type Node.
         * @param child
         * @return
         */
        public int getChildCount(Object parent) {
            Node parentNode = (Node) parent;
            return parentNode.getChildren().size();
        }
    
        /**
         * Returns the index of child within the given parent.
         * Returns -1 if child is not a child of parent.
         * Assumes that the parent is an object of type Node.
         * @param child
         * @return
         */
        public int getIndexOfChild(Object parent, Object child) {
            Node parentNode = (Node) parent;
            return parentNode.getChildren().indexOf(child);
        }
    
        /**
         * Returns true if the given node does not have any children.
         * Assumes that node is an object of type Node.
         * @param child
         * @return
         */
        public boolean isLeaf(Object node) {
            Node someNode = (Node) node;
            return someNode.getChildren().isEmpty();
        }
    
        /**
         * Removes all nodes, within the iterable, from this model.
         * If an object from the iterable is not a Node this method will throw an exception.
         * @param nodes
         */
        public void removeNodes(Iterable<Object> nodes) {
            for (Object obj : nodes) {
                Node node = (Node) obj;
                Node parent = node.getParent();
    
                parent.removeChild(node);
            }
        }
    
        /**
         * Adds all nodes, within the iterable, as children to the given parent.
         * Starts the insertion at startIndex and counts up by one for each insertion.
         * If an object from the iterable is not a Node this method will throw an exception.
         * @param parent
         * @param startIndex
         * @param nodes
         */
        public void insertNodes(Object parent, int startIndex, Iterable<Object> nodes) {
            Node parentNode = (Node) parent;
            if (startIndex > parentNode.getChildren().size()) {
                startIndex = parentNode.getChildren().size();
            }
            for (Object obj : nodes) {
                Node child = (Node) obj;
                parentNode.addChild(child, startIndex++);
            }
        }
    
        /**
         * Not used and not implement.
         * Will throw an {@link UnsupportedOperationException} if called.
         */
        public void valueForPathChanged(TreePath path, Object newValue) {
            // Never being used.
            throw new UnsupportedOperationException("Not implemented.");
        }
    
        public void addTreeModelListener(TreeModelListener l) {
            listeners.add(l);
        }
    
        public void removeTreeModelListener(TreeModelListener l) {
            listeners.remove(l);
        }
    
        /**
         * Constructs a TreeModelEvent for the given node and index 
         * and calls treeNodesInserted on all registered listeners.
         * The node must never be null.
         * @param node
         * @param index
         */
        protected void fireInsertEvent(Node node, int index) {
            TreeModelEvent e = makeEvent(node, index);
            for (TreeModelListener l : listeners) {
                l.treeNodesInserted(e);
            }
        }
    
        /**
         * Constructs a TreeModelEvent for the given node and index 
         * and calls treeNodesRemoved on all registered listeners.
         * The node must never be null.
         * @param node
         * @param index
         */
        protected void fireRemoveEvent(Node node, int index) {
            TreeModelEvent e = makeEvent(node, index);
            for (TreeModelListener l : listeners) {
                l.treeNodesRemoved(e);
            }
        }
    
        /**
         * Creates a TreeModelEvent for the given node and index.
         * The node must never be null.
         * @param node
         * @param index
         * @return
         */
        protected TreeModelEvent makeEvent(Node node, int index) {
            return new TreeModelEvent(this, makePath(node), asArray(index), asArray(node));
        }
    
        /**
         * Creates a {@link TreePath} for the given node.
         * The last component in the path will be the given node.
         * The root of the tree will not be a part of the path.
         * @param node
         * @return
         */
        protected TreePath makePath(Object node) {
            if (node == null) {
                throw new NullPointerException();
            }
            Deque<Object> pathAsStack = new LinkedList<>();
            Object current = node;
            while (current != null) {
                pathAsStack.add(current);
                current = getParent(current);
            }
            Object[] pathAsArray = new Object[pathAsStack.size() - 1];
            int index = 0;
            while (pathAsStack.size() > 1) {
                pathAsArray[index++] = pathAsStack.pollLast();
            }
            return new TreePath(pathAsArray);
        }
    
        /**
         * Simple wrapper.
         * @param index
         * @return
         */
        protected int[] asArray(int index) {
            return new int[] {index};
        }
    
        /**
         * Simple wrapper.
         * @param index
         * @return
         */
        protected Object[] asArray(Object obj) {
            return new Object[] {obj};
        }
    
    }
    
    package pkg;
    
    import java.awt.datatransfer.DataFlavor;
    import java.awt.datatransfer.Transferable;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.swing.JComponent;
    import javax.swing.JTree;
    import javax.swing.TransferHandler;
    import javax.swing.tree.TreePath;
    
    public class JTreeTransferHandler extends TransferHandler {
        private static final long serialVersionUID = 1L;
    
        // This flavor will be used for the transfers.
        private final DataFlavor nodeFlavor;
    
        public JTreeTransferHandler() {
            // We always transfer a List of objects.
            nodeFlavor = new DataFlavor(List.class, List.class.getSimpleName());
        }
    
        /*
         * Next three methods will handle the canImport functionality.
         * canImport determines whether an import can take place or is rejected.
         * We will treat this differently for Drag & Drop and Copy-Cut-Paste.
         */
    
        public boolean canImport(TransferHandler.TransferSupport support) {
            try {
                // First, check for the right flavor.
                if (!support.isDataFlavorSupported(nodeFlavor)) {
                    return false;
                }
                // Then, handle the special cases.
                if (support.isDrop()) {
                    return canImportDrop(support);
                } else {
                    return canImportPaste(support);
                }
            } catch (Exception e) {
                /* 
                 * We do this because otherwise the exception would be swallowed by swing
                 * and we wont know what happened.
                 */
                e.printStackTrace();
                throw e;
            }
        }
    
        private boolean canImportDrop(TransferHandler.TransferSupport support) {
            support.setShowDropLocation(true);
    
            /*
             * Can not drop a path on itself or on a descendant of itself.
             * We know, that the component is a JTree.
             */
            JTree tree = (JTree) support.getComponent();
    
            JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
            TreePath dropPath = dl.getPath();
    
            /* 
             * If one of the selected paths is supposed to be dropped on 
             * itself or a descendant of itself, return false.
             */
            TreePath[] selectedPaths = tree.getSelectionPaths();
            for (TreePath selectedPath : selectedPaths) {
                if (selectedPath.isDescendant(dropPath)) {
                    return false;
                }
            }
            // Otherwise, return true.
            return true;
        }
    
        private boolean canImportPaste(TransferHandler.TransferSupport support) {
            /*
             * Can only paste nodes if tree has exactly one path selected.
             * Otherwise the paste location is not known...
             */
            JTree tree = (JTree) support.getComponent();
            TreePath[] selectedPaths = tree.getSelectionPaths();
            return selectedPaths.length == 1 && selectedPaths[0] != null;
        }
    
        /*
         * Next three methods will handle the importData functionality.
         * importData will insert the data into our model.
         * We will treat this differently for Drag & Drop and Copy-Cut-Paste.
         */
    
        public boolean importData(TransferHandler.TransferSupport support) {
            try {
                // Check if we can import.
                if(!canImport(support)) {
                    return false;
                }
                // Handle the different situations.
                if (support.isDrop()) {
                    return importDataDrop(support);
                } else {
                    return importDataPaste(support);
                }
            } catch (Exception e) {
                /* 
                 * We do this because otherwise the exception would be swallowed by swing
                 * and we wont know what happened.
                 */
                e.printStackTrace();
                throw e;
            }
        }
    
        private boolean importDataDrop(TransferHandler.TransferSupport support) {
            /* 
             * When dropped the action is a MOVE command.
             * We must first remove the old data, and then insert the new data.
             */
            List<Object> data = extractImportData(support);
    
            /*
             * We know, that the component is always a JTree and the model is always a MyModel.
             */
            JTree tree = (JTree) support.getComponent();
            MyModel model = (MyModel) tree.getModel();
    
            // Extract drop location and drop index
            JTree.DropLocation dl = (JTree.DropLocation)support.getDropLocation();
    
            TreePath destPath = dl.getPath();
            Object parent = destPath.getLastPathComponent();
            int index = dl.getChildIndex();
    
            if (index == -1) {
                // Drop location is on top of a node
                index = model.getChildCount(parent);
            }
    
            // First remove data
            model.removeNodes(data);
            // Then insert data
            model.insertNodes(parent, index, data);
    
            return true;
        }
    
        private boolean importDataPaste(TransferHandler.TransferSupport support) {
            /* 
             * This is either a copy & paste or a cut & paste.
             * If this was a copy & paste we need to clone the data!
             * If this was a cut & paste we can simply insert it.
             * 
             * Unfortunately, there is no good way to know...
             */
    
            List<Object> data = extractImportData(support);
    
            // no way to know... what a bummer.
            int action = MOVE;
            if ((action & COPY) == COPY) {
                // When we copy, then clone the list data!
                // somehow clone the data...
            }
    
            /*
             * We know, that the component is always a JTree and the model is always a MyModel.
             */
            JTree tree = (JTree) support.getComponent();
            MyModel model = (MyModel) tree.getModel();
    
            // Extract drop location and drop index
            // Drop location depends on selection
            TreePath destPath = tree.getSelectionPath();
            Object parent;
            // Path can be null if nothing is selected.
            if (destPath == null) {
                parent = model.getRoot();
            } else {
                parent = destPath.getLastPathComponent();
            }
            int index = model.getChildCount(parent);
    
            /* 
             * Inserts the new nodes into the model. 
             * Nodes must NOT be contained in the model at this point!
             */
            model.insertNodes(parent, index, data);
    
            return true;
        }
    
        /*
         * This method handles the removal of data if the action was a Cut.
         */
    
        protected void exportDone(JComponent c, Transferable data, int action) {
            // Only a move action needs to remove the old data.
            if (action != MOVE) {
                return;
            }
            /* 
             * When this is a drag & drop, do nothing.
             * When this was a cut, then remove the old data.
             */
    
            // no way to know... what a bummer.
            boolean isDragAndDrop = true;
            if (!isDragAndDrop) {
                // Extract nodes from data
                List<Object> nodes = extractImportData(data);
    
                // The component is always a JTree and always has a TreeModel2 as its model
                JTree tree = (JTree) c;
                MyModel model = (MyModel) tree.getModel();
    
                // Remove the nodes from the model
                // This will throw an exception if the nodes are not contained in the model!
                model.removeNodes(nodes);
            }
        }
    
        /*
         * Creates our Transferable as a list of all selected paths in the tree.
         */
    
        protected Transferable createTransferable(JComponent c) {
            try {
                // Component is always a JTree
                JTree tree = (JTree) c;
    
                // Extract nodes to be transfered => Always the selected nodes
                TreePath[] paths = tree.getSelectionPaths();
                if(paths != null) {
                    List<Object> nodeList = new ArrayList<>(); 
                    for (TreePath path : paths) {
                        nodeList.add(path.getLastPathComponent());
                    }
                    return new ObjectTransferable<List<Object>>(nodeList, nodeFlavor);
                }
                return null;
            } catch (Exception e) {
                /* 
                 * We do this because otherwise the exception would be swallowed by swing
                 * and we wont know what happened.
                 */
                e.printStackTrace();
                throw e;
            }
        }
    
        public int getSourceActions(JComponent c) {
            return COPY_OR_MOVE;
        }
    
        /*
         * Utility methods for extracting data from a transfer.
         */
    
        private List<Object> extractImportData(TransferHandler.TransferSupport support) {
            return extractImportData(support.getTransferable());
        }
    
        @SuppressWarnings("unchecked")
        private List<Object> extractImportData(Transferable trans) {
            try {
                return (List<Object>) trans.getTransferData(nodeFlavor);
            } catch (Exception e) {
                // We dont need a checked exception because we wont do anything with it anyways.
                throw new RuntimeException(e);
            }
        }
    
    }
    
    package pkg;
    
    import java.awt.EventQueue;
    
    import javax.swing.JFrame;
    import javax.swing.JScrollPane;
    
    import java.awt.BorderLayout;
    import java.awt.event.InputEvent;
    import java.awt.event.KeyEvent;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Random;
    
    import javax.swing.Action;
    import javax.swing.DropMode;
    import javax.swing.JTree;
    import javax.swing.JMenuBar;
    import javax.swing.JMenu;
    import javax.swing.JMenuItem;
    import javax.swing.KeyStroke;
    import javax.swing.TransferHandler;
    import javax.swing.tree.TreeSelectionModel;
    
    public class App {
    
        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    try {
                        new App();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
        /**
         * The number of nodes that will be randomly constructed. Must be smaller then NODE_NAMES.length.
         */
        private static final int NODE_COUNT = 10;
        /**
         * An array containing random names that will be used for constructing the tree.
         */
        private static final String[] NODE_NAMES = new String[] {
            "Albert",   "Annabell", "Benjamin", "Bella",        "Cedric",       "Cecile",
            "David",    "Danielle", "Emanuel",  "Elisabeth",    "Frederick",    "Felicita",
            "Georg",    "Giselle",  "Hans",     "Henriette",    "Ismael",       "Irene",
            "Joshua",   "Joceline", "Kyle",     "Kaithlin",     "Lyod",         "Lisa",
            "Michael",  "Michelle", "Norbert",  "Nele",         "Olaf",         "Ophelia",
            "Robert",   "Renate",   "Stuart",   "Sabrina",      "Theo",         "Tania",
            "Ulric",    "Ursula",   "Victor",   "Veronica",     "William",      "Wilma"
        };
    
        /*
         * If the static final variables have illegal values we will throw an exception at class initialization.
         */
        static {
            if (NODE_NAMES.length < NODE_COUNT) {
                throw new RuntimeException("Node count must be no bigger then: "+NODE_NAMES.length);
            }
        }
    
        public App() {
            // Setup the frame
            JFrame frmTreeModelTest = new JFrame();
            frmTreeModelTest.setTitle("JTree Transfer Handler Test");
            frmTreeModelTest.setSize(600, 480);
            frmTreeModelTest.setLocationRelativeTo(null);
            frmTreeModelTest.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            // Scroll panel for the tree
            JScrollPane scrollPane = new JScrollPane();
            frmTreeModelTest.getContentPane().add(scrollPane, BorderLayout.CENTER);
    
            /*
             * Construct our initial nodes.
             * This will create a random tree which contains all kinds of names.
             */
            Node rootNode = new Node("Root");
            List<String> possibleNames = new ArrayList<>(Arrays.asList(NODE_NAMES));
            List<Node> existingNodes = new ArrayList<>();
            existingNodes.add(rootNode);
            Random random = new Random();
            for (int i = 0; i < NODE_COUNT; i++) {
                int nameID = random.nextInt(possibleNames.size());
                Node node = new Node(possibleNames.remove(nameID));
    
                int parentID = random.nextInt(existingNodes.size());
                Node parent = existingNodes.get(parentID);
    
                parent.addChild(node);
                existingNodes.add(node);
            }
    
            // The JTree that will be used for this test
            JTree tree = new JTree();
            tree.setModel(new MyModel(rootNode));   
            tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
            tree.setTransferHandler(new JTreeTransferHandler());
            tree.setDragEnabled(true);
            tree.setDropMode(DropMode.ON_OR_INSERT);
    
            /*
             * This code was taken from the oracle tutorial website for Copy-Cut-Paste support.
             * http://docs.oracle.com/javase/tutorial/uiswing/dnd/listpaste.html
             */
            tree.getActionMap().put(TransferHandler.getCutAction().getValue(Action.NAME), TransferHandler.getCutAction());
            tree.getActionMap().put(TransferHandler.getCopyAction().getValue(Action.NAME), TransferHandler.getCopyAction());
            tree.getActionMap().put(TransferHandler.getPasteAction().getValue(Action.NAME), TransferHandler.getPasteAction());
    
            scrollPane.setViewportView(tree);
    
            // Construct the menu bar with CCP functionality.
            JMenuBar menuBar = new JMenuBar();
            frmTreeModelTest.setJMenuBar(menuBar);
    
            JMenu mnEdit = new JMenu("Edit");
            menuBar.add(mnEdit);
    
            JMenuItem mntmCopy = new JMenuItem("Copy");
            mntmCopy.addActionListener(TransferActionListener.INSTANCE);
            mntmCopy.setActionCommand((String) TransferHandler.getCopyAction().getValue(Action.NAME));
            mntmCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK));
            mnEdit.add(mntmCopy);
    
            JMenuItem mntmCut = new JMenuItem("Cut");
            mntmCut.addActionListener(TransferActionListener.INSTANCE);
            mntmCut.setActionCommand((String) TransferHandler.getCutAction().getValue(Action.NAME));
            mntmCut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK));
            mnEdit.add(mntmCut);
    
            JMenuItem mntmPaste = new JMenuItem("Paste");
            mntmPaste.addActionListener(TransferActionListener.INSTANCE);
            mntmPaste.setActionCommand((String) TransferHandler.getPasteAction().getValue(Action.NAME));
            mntmPaste.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK));
            mnEdit.add(mntmPaste);
    
            // Show the frame
            frmTreeModelTest.setVisible(true);
        }
    
    }
    
    public class JTreeTransferHandler extends TransferHandler {
    
    private final DataFlavor nodeFlavor;
    
    Boolean isCut;
    Boolean isdrag = false;
    
    @Override
    public int getSourceActions(JComponent c) {
        return COPY_OR_MOVE;
    }
    
    @Override
    protected Transferable createTransferable(JComponent source) {
    
    }
    
    @Override
    protected void exportDone(JComponent source, Transferable data, int action) {
        isCut = action == MOVE; //to check whether the operation is cut or copy
        if (isdrag) {
            if (isCut) {
                //Implement you drag code (normal drag)
            } else {
                //Implement you ctrl+drag code
            }
        }
        isdrag = false; //resetting the dnd flag
    }
    
    @Override
    public boolean canImport(TransferHandler.TransferSupport support) {
        if (!support.isDataFlavorSupported(DataFlavors.nodeFlavor)) {
            return false;
        }
        if (support.isDrop()) {
            return canImportDnd();
        } else {
            return canImportccp();
        }
        return false;
    }
    
    @Override
    public boolean importData(TransferHandler.TransferSupport support) {
        if (!canImport(support)) {
            return false;
        }
        if (support.isDrop()) {
            isdrag = true;//To know whether it is a drag and drop in exportdone
            if (support.getDropAction() == MOVE) {
                //Implement you drag code (normal drag)
            } else if (support.getDropAction() == COPY) {
                //Implement you ctrl+drag code
            }
        } else if (isCut) {
            //Implement you cut ctrl+x code
        }
    
        return true;
    }
    
    }