3789983e89
Reviewed-by: darcy, ihse
402 lines
14 KiB
Java
402 lines
14 KiB
Java
/*
|
|
* 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
|
|
* @key headful
|
|
* @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);
|
|
}
|
|
}
|
|
}
|