8129830: JTree drag/drop on lower half of last child of container incorrect.
Reviewed-by: alexsch, azvegint
--- 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);
--- 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)));
+ }
}
}
--- /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<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);
+ }
+ }
+}
\ No newline at end of file