# HG changeset patch # User ssadetsky # Date 1435677998 -10800 # Node ID 6b5f36a9a3c0cfbb00b8f36cd9a54788e6a0f85a # Parent 786638581dba4c27e7831be06018d7980d133c13 8129830: JTree drag/drop on lower half of last child of container incorrect. Reviewed-by: alexsch, azvegint diff -r 786638581dba -r 6b5f36a9a3c0 jdk/src/java.desktop/share/classes/javax/swing/JTree.java --- a/jdk/src/java.desktop/share/classes/javax/swing/JTree.java Tue Jun 30 11:04:58 2015 +0300 +++ b/jdk/src/java.desktop/share/classes/javax/swing/JTree.java Tue Jun 30 18:26:38 2015 +0300 @@ -1365,6 +1365,13 @@ child = getPathForRow(index); parent = child.getParentPath(); + TreePath prev = getPathForRow(row).getParentPath(); + if (prev != null && !prev.equals(parent)) { + location = new DropLocation(p, prev, + model.getChildCount(prev.getLastPathComponent())); + break; + } + } else { assert checkOn; location = new DropLocation(p, getPathForRow(row), -1); diff -r 786638581dba -r 6b5f36a9a3c0 jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicTreeUI.java --- a/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicTreeUI.java Tue Jun 30 11:04:58 2015 +0300 +++ b/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicTreeUI.java Tue Jun 30 18:26:38 2015 +0300 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -1497,8 +1497,16 @@ rect.x = xRect.x; rect.width = xRect.width; } else { - rect = tree.getPathBounds(path.pathByAddingChild( - model.getChild(path.getLastPathComponent(), index))); + if (index >= model.getChildCount(path.getLastPathComponent())) { + rect = tree.getPathBounds(path.pathByAddingChild( + model.getChild(path.getLastPathComponent(), + index - 1))); + rect.y = rect.y + rect.height; + } else { + rect = tree.getPathBounds(path.pathByAddingChild( + model.getChild(path.getLastPathComponent(), + index))); + } } } diff -r 786638581dba -r 6b5f36a9a3c0 jdk/test/javax/swing/JTree/DnD/LastNodeLowerHalfDrop.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/javax/swing/JTree/DnD/LastNodeLowerHalfDrop.java Tue Jun 30 18:26:38 2015 +0300 @@ -0,0 +1,384 @@ +/* + * 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 + @summary JTree drag/drop on lower half of last child of container incorrect + @author Semyon Sadetsky + */ + +import java.awt.*; +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.*; +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())) { + 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())) { + throw new RuntimeException("c1 was not inserted beetween c and b nodes"); + } + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + f.dispose(); + } + }); + System.out.printf("ok"); + } + + static void testCase(DefaultMutableTreeNode drag, + DefaultMutableTreeNode drop, 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(400); + robot.mouseMove(dropPoint.x, dropPoint.y); + robot.delay(400); + robot.mouseRelease(InputEvent.BUTTON1_MASK); + + 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 copies = new ArrayList<>(); + List 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); + } + } +} \ No newline at end of file