| /* |
| * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* @test |
| * @bug 8129830 8132771 |
| * @summary JTree drag/drop on lower half of last child of container incorrect |
| * @run main LastNodeLowerHalfDrop |
| */ |
| |
| import java.awt.Point; |
| import java.awt.Rectangle; |
| import java.awt.Robot; |
| import java.awt.datatransfer.DataFlavor; |
| import java.awt.datatransfer.Transferable; |
| import java.awt.datatransfer.UnsupportedFlavorException; |
| import java.awt.event.InputEvent; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.List; |
| import javax.swing.DropMode; |
| import javax.swing.JComponent; |
| import javax.swing.JFrame; |
| import javax.swing.JScrollPane; |
| import javax.swing.JTree; |
| import javax.swing.SwingUtilities; |
| import javax.swing.TransferHandler; |
| import javax.swing.tree.DefaultMutableTreeNode; |
| import javax.swing.tree.DefaultTreeModel; |
| import javax.swing.tree.TreeModel; |
| import javax.swing.tree.TreeNode; |
| import javax.swing.tree.TreePath; |
| import javax.swing.tree.TreeSelectionModel; |
| |
| public class LastNodeLowerHalfDrop { |
| |
| private static DefaultMutableTreeNode b1; |
| private static DefaultMutableTreeNode b2; |
| private static DefaultMutableTreeNode c; |
| private static JTree jTree; |
| private static DefaultMutableTreeNode a; |
| private static DefaultMutableTreeNode b; |
| private static DefaultMutableTreeNode a1; |
| private static Point dragPoint; |
| private static Point dropPoint; |
| private static JFrame f; |
| private static DefaultMutableTreeNode c1; |
| private static DefaultMutableTreeNode root; |
| |
| |
| public static void main(String[] args) throws Exception { |
| SwingUtilities.invokeAndWait(new Runnable() { |
| @Override |
| public void run() { |
| f = new JFrame(); |
| f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); |
| f.add(new LastNodeLowerHalfDrop().getContent()); |
| f.setSize(400, 400); |
| f.setLocationRelativeTo(null); |
| f.setVisible(true); |
| } |
| }); |
| testCase(b2, a1, +0.4f); |
| if (!"b2".equals(jTree.getModel(). |
| getChild(a, a.getChildCount() - 1).toString())) { |
| cleanUp(); |
| throw new RuntimeException("b1 was not inserted " |
| +"in the last position in a"); |
| } |
| testCase(c1, c, -0.4f); |
| if (!"c1".equals(jTree.getModel().getChild(root, 2).toString())) { |
| cleanUp(); |
| throw new RuntimeException("c1 was not inserted " |
| +"between c and b nodes"); |
| } |
| cleanUp(); |
| } |
| |
| private static void cleanUp() throws Exception { |
| SwingUtilities.invokeAndWait(new Runnable() { |
| @Override |
| public void run() { |
| f.dispose(); |
| } |
| }); |
| } |
| |
| private static void testCase(final DefaultMutableTreeNode drag, |
| final DefaultMutableTreeNode drop, final float shift) |
| throws Exception { |
| Robot robot = new Robot(); |
| robot.waitForIdle(); |
| SwingUtilities.invokeAndWait(new Runnable() { |
| @Override |
| public void run() { |
| Rectangle rectDrag = |
| jTree.getPathBounds(new TreePath(drag.getPath())); |
| dragPoint = new Point((int)rectDrag.getCenterX(), |
| (int) rectDrag.getCenterY()); |
| SwingUtilities.convertPointToScreen(dragPoint, jTree); |
| Rectangle rectDrop = |
| jTree.getPathBounds(new TreePath(drop.getPath())); |
| dropPoint = new Point(rectDrop.x + 5, |
| (int) (rectDrop.getCenterY() + shift * rectDrop.height)); |
| SwingUtilities.convertPointToScreen(dropPoint, jTree); |
| } |
| }); |
| |
| robot.mouseMove(dragPoint.x, dragPoint.y); |
| robot.mousePress(InputEvent.BUTTON1_MASK); |
| robot.delay(1000); |
| robot.mouseMove(dropPoint.x, dropPoint.y); |
| robot.delay(1000); |
| robot.mouseRelease(InputEvent.BUTTON1_MASK); |
| robot.delay(1000); |
| robot.waitForIdle(); |
| } |
| |
| private JScrollPane getContent() { |
| jTree = new JTree(getTreeModel()); |
| jTree.setRootVisible(false); |
| jTree.setDragEnabled(true); |
| jTree.setDropMode(DropMode.INSERT); |
| jTree.setTransferHandler(new TreeTransferHandler()); |
| jTree.getSelectionModel().setSelectionMode( |
| TreeSelectionModel.SINGLE_TREE_SELECTION); |
| expandTree(jTree); |
| return new JScrollPane(jTree); |
| } |
| |
| protected static TreeModel getTreeModel() { |
| root = new DefaultMutableTreeNode("Root"); |
| |
| a = new DefaultMutableTreeNode("A"); |
| root.add(a); |
| a1 = new DefaultMutableTreeNode("a1"); |
| a.add(a1); |
| |
| b = new DefaultMutableTreeNode("B"); |
| root.add(b); |
| b1 = new DefaultMutableTreeNode("b1"); |
| b.add(b1); |
| b2 = new DefaultMutableTreeNode("b2"); |
| b.add(b2); |
| |
| c = new DefaultMutableTreeNode("C"); |
| root.add(c); |
| c1 = new DefaultMutableTreeNode("c1"); |
| c.add(c1); |
| return new DefaultTreeModel(root); |
| } |
| |
| private void expandTree(JTree tree) { |
| DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel() |
| .getRoot(); |
| Enumeration e = root.breadthFirstEnumeration(); |
| while (e.hasMoreElements()) { |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement(); |
| if (node.isLeaf()) { |
| continue; |
| } |
| int row = tree.getRowForPath(new TreePath(node.getPath())); |
| tree.expandRow(row); |
| } |
| } |
| } |
| |
| class TreeTransferHandler extends TransferHandler { |
| DataFlavor nodesFlavor; |
| DataFlavor[] flavors = new DataFlavor[1]; |
| DefaultMutableTreeNode[] nodesToRemove; |
| |
| public TreeTransferHandler() { |
| try { |
| String mimeType = DataFlavor.javaJVMLocalObjectMimeType |
| + ";class=\"" |
| + javax.swing.tree.DefaultMutableTreeNode[].class.getName() |
| + "\""; |
| nodesFlavor = new DataFlavor(mimeType); |
| flavors[0] = nodesFlavor; |
| } catch (ClassNotFoundException e) { |
| System.out.println("ClassNotFound: " + e.getMessage()); |
| } |
| } |
| |
| @Override |
| public boolean canImport(TransferHandler.TransferSupport support) { |
| if (!support.isDrop()) { |
| return false; |
| } |
| support.setShowDropLocation(true); |
| if (!support.isDataFlavorSupported(nodesFlavor)) { |
| return false; |
| } |
| // Do not allow a drop on the drag source selections. |
| JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation(); |
| JTree tree = (JTree) support.getComponent(); |
| int dropRow = tree.getRowForPath(dl.getPath()); |
| int[] selRows = tree.getSelectionRows(); |
| for (int i = 0; i < selRows.length; i++) { |
| if (selRows[i] == dropRow) { |
| return false; |
| } |
| } |
| // Do not allow MOVE-action drops if a non-leaf node is |
| // selected unless all of its children are also selected. |
| int action = support.getDropAction(); |
| if (action == MOVE) { |
| return haveCompleteNode(tree); |
| } |
| // Do not allow a non-leaf node to be copied to a level |
| // which is less than its source level. |
| TreePath dest = dl.getPath(); |
| DefaultMutableTreeNode target = (DefaultMutableTreeNode) |
| dest.getLastPathComponent(); |
| TreePath path = tree.getPathForRow(selRows[0]); |
| DefaultMutableTreeNode firstNode = (DefaultMutableTreeNode) |
| path.getLastPathComponent(); |
| if (firstNode.getChildCount() > 0 |
| && target.getLevel() < firstNode.getLevel()) { |
| return false; |
| } |
| return true; |
| } |
| |
| private boolean haveCompleteNode(JTree tree) { |
| int[] selRows = tree.getSelectionRows(); |
| TreePath path = tree.getPathForRow(selRows[0]); |
| DefaultMutableTreeNode first = (DefaultMutableTreeNode) |
| path.getLastPathComponent(); |
| int childCount = first.getChildCount(); |
| // first has children and no children are selected. |
| if (childCount > 0 && selRows.length == 1) { |
| return false; |
| } |
| // first may have children. |
| for (int i = 1; i < selRows.length; i++) { |
| path = tree.getPathForRow(selRows[i]); |
| DefaultMutableTreeNode next = (DefaultMutableTreeNode) |
| path.getLastPathComponent(); |
| if (first.isNodeChild(next)) { |
| // Found a child of first. |
| if (childCount > selRows.length - 1) { |
| // Not all children of first are selected. |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| protected Transferable createTransferable(JComponent c) { |
| JTree tree = (JTree) c; |
| TreePath[] paths = tree.getSelectionPaths(); |
| if (paths != null) { |
| // Make up a node array of copies for transfer and |
| // another for/of the nodes that will be removed in |
| // exportDone after a successful drop. |
| List<DefaultMutableTreeNode> copies = new ArrayList<>(); |
| List<DefaultMutableTreeNode> toRemove = new ArrayList<>(); |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode) |
| paths[0].getLastPathComponent(); |
| DefaultMutableTreeNode copy = copy(node); |
| copies.add(copy); |
| toRemove.add(node); |
| for (int i = 1; i < paths.length; i++) { |
| DefaultMutableTreeNode next = (DefaultMutableTreeNode) paths[i] |
| .getLastPathComponent(); |
| // Do not allow higher level nodes to be added to list. |
| if (next.getLevel() < node.getLevel()) { |
| break; |
| } else if (next.getLevel() > node.getLevel()) { // child node |
| copy.add(copy(next)); |
| // node already contains child |
| } else { // sibling |
| copies.add(copy(next)); |
| toRemove.add(next); |
| } |
| } |
| DefaultMutableTreeNode[] nodes = copies |
| .toArray(new DefaultMutableTreeNode[copies.size()]); |
| nodesToRemove = toRemove.toArray( |
| new DefaultMutableTreeNode[toRemove.size()]); |
| return new NodesTransferable(nodes); |
| } |
| return null; |
| } |
| |
| /** |
| * Defensive copy used in createTransferable. |
| */ |
| private DefaultMutableTreeNode copy(TreeNode node) { |
| return new DefaultMutableTreeNode(node); |
| } |
| |
| @Override |
| protected void exportDone(JComponent source, Transferable data, int action) { |
| if ((action & MOVE) == MOVE) { |
| JTree tree = (JTree) source; |
| DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); |
| // Remove nodes saved in nodesToRemove in createTransferable. |
| for (DefaultMutableTreeNode nodesToRemove1 : nodesToRemove) { |
| model.removeNodeFromParent(nodesToRemove1); |
| } |
| } |
| } |
| |
| @Override |
| public int getSourceActions(JComponent c) { |
| return COPY_OR_MOVE; |
| } |
| |
| @Override |
| public boolean importData(TransferHandler.TransferSupport support) { |
| if (!canImport(support)) { |
| return false; |
| } |
| // Extract transfer data. |
| DefaultMutableTreeNode[] nodes = null; |
| try { |
| Transferable t = support.getTransferable(); |
| nodes = (DefaultMutableTreeNode[]) t.getTransferData(nodesFlavor); |
| } catch (UnsupportedFlavorException ufe) { |
| System.out.println("UnsupportedFlavor: " + ufe.getMessage()); |
| } catch (java.io.IOException ioe) { |
| System.out.println("I/O error: " + ioe.getMessage()); |
| } |
| // Get drop location info. |
| JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation(); |
| int childIndex = dl.getChildIndex(); |
| TreePath dest = dl.getPath(); |
| DefaultMutableTreeNode parent = (DefaultMutableTreeNode) |
| dest.getLastPathComponent(); |
| JTree tree = (JTree) support.getComponent(); |
| DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); |
| // Configure for drop mode. |
| int index = childIndex; // DropMode.INSERT |
| if (childIndex == -1) { // DropMode.ON |
| index = parent.getChildCount(); |
| } |
| // Add data to model. |
| for (DefaultMutableTreeNode node : nodes) { |
| model.insertNodeInto(node, parent, index++); |
| } |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getName(); |
| } |
| |
| public class NodesTransferable implements Transferable { |
| DefaultMutableTreeNode[] nodes; |
| |
| public NodesTransferable(DefaultMutableTreeNode[] nodes) { |
| this.nodes = nodes; |
| } |
| |
| @Override |
| public Object getTransferData(DataFlavor flavor) |
| throws UnsupportedFlavorException { |
| if (!isDataFlavorSupported(flavor)) { |
| throw new UnsupportedFlavorException(flavor); |
| } |
| return nodes; |
| } |
| |
| @Override |
| public DataFlavor[] getTransferDataFlavors() { |
| return flavors; |
| } |
| |
| @Override |
| public boolean isDataFlavorSupported(DataFlavor flavor) { |
| return nodesFlavor.equals(flavor); |
| } |
| } |
| } |