1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* @test 25 * @bug 8129830 8132771 26 * @summary JTree drag/drop on lower half of last child of container incorrect 27 * @run main LastNodeLowerHalfDrop 28 */ 29 30 import java.awt.Point; 31 import java.awt.Rectangle; 32 import java.awt.Robot; 33 import java.awt.datatransfer.DataFlavor; 34 import java.awt.datatransfer.Transferable; 35 import java.awt.datatransfer.UnsupportedFlavorException; 36 import java.awt.event.InputEvent; 37 import java.util.ArrayList; 38 import java.util.Enumeration; 39 import java.util.List; 40 import javax.swing.DropMode; 41 import javax.swing.JComponent; 42 import javax.swing.JFrame; 43 import javax.swing.JScrollPane; 44 import javax.swing.JTree; 45 import javax.swing.SwingUtilities; 46 import javax.swing.TransferHandler; 47 import javax.swing.tree.DefaultMutableTreeNode; 48 import javax.swing.tree.DefaultTreeModel; 49 import javax.swing.tree.TreeModel; 50 import javax.swing.tree.TreeNode; 51 import javax.swing.tree.TreePath; 52 import javax.swing.tree.TreeSelectionModel; 53 54 public class LastNodeLowerHalfDrop { 55 56 private static DefaultMutableTreeNode b1; 57 private static DefaultMutableTreeNode b2; 58 private static DefaultMutableTreeNode c; 59 private static JTree jTree; 60 private static DefaultMutableTreeNode a; 61 private static DefaultMutableTreeNode b; 62 private static DefaultMutableTreeNode a1; 63 private static Point dragPoint; 64 private static Point dropPoint; 65 private static JFrame f; 66 private static DefaultMutableTreeNode c1; 67 private static DefaultMutableTreeNode root; 68 69 70 public static void main(String[] args) throws Exception { 71 SwingUtilities.invokeAndWait(new Runnable() { 72 @Override 73 public void run() { 74 f = new JFrame(); 75 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 76 f.add(new LastNodeLowerHalfDrop().getContent()); 77 f.setSize(400, 400); 78 f.setLocationRelativeTo(null); 79 f.setVisible(true); 80 } 81 }); 82 testCase(b2, a1, +0.4f); 83 if (!"b2".equals(jTree.getModel(). 84 getChild(a, a.getChildCount() - 1).toString())) { 85 cleanUp(); 86 throw new RuntimeException("b1 was not inserted " 87 +"in the last position in a"); 88 } 89 testCase(c1, c, -0.4f); 90 if (!"c1".equals(jTree.getModel().getChild(root, 2).toString())) { 91 cleanUp(); 92 throw new RuntimeException("c1 was not inserted " 93 +"between c and b nodes"); 94 } 95 cleanUp(); 96 } 97 98 private static void cleanUp() throws Exception { 99 SwingUtilities.invokeAndWait(new Runnable() { 100 @Override 101 public void run() { 102 f.dispose(); 103 } 104 }); 105 } 106 107 private static void testCase(final DefaultMutableTreeNode drag, 108 final DefaultMutableTreeNode drop, final float shift) 109 throws Exception { 110 Robot robot = new Robot(); 111 robot.waitForIdle(); 112 SwingUtilities.invokeAndWait(new Runnable() { 113 @Override 114 public void run() { 115 Rectangle rectDrag = 116 jTree.getPathBounds(new TreePath(drag.getPath())); 117 dragPoint = new Point((int)rectDrag.getCenterX(), 118 (int) rectDrag.getCenterY()); 119 SwingUtilities.convertPointToScreen(dragPoint, jTree); 120 Rectangle rectDrop = 121 jTree.getPathBounds(new TreePath(drop.getPath())); 122 dropPoint = new Point(rectDrop.x + 5, 123 (int) (rectDrop.getCenterY() + shift * rectDrop.height)); 124 SwingUtilities.convertPointToScreen(dropPoint, jTree); 125 } 126 }); 127 128 robot.mouseMove(dragPoint.x, dragPoint.y); 129 robot.mousePress(InputEvent.BUTTON1_MASK); 130 robot.delay(1000); 131 robot.mouseMove(dropPoint.x, dropPoint.y); 132 robot.delay(1000); 133 robot.mouseRelease(InputEvent.BUTTON1_MASK); 134 robot.delay(1000); 135 robot.waitForIdle(); 136 } 137 138 private JScrollPane getContent() { 139 jTree = new JTree(getTreeModel()); 140 jTree.setRootVisible(false); 141 jTree.setDragEnabled(true); 142 jTree.setDropMode(DropMode.INSERT); 143 jTree.setTransferHandler(new TreeTransferHandler()); 144 jTree.getSelectionModel().setSelectionMode( 145 TreeSelectionModel.SINGLE_TREE_SELECTION); 146 expandTree(jTree); 147 return new JScrollPane(jTree); 148 } 149 150 protected static TreeModel getTreeModel() { 151 root = new DefaultMutableTreeNode("Root"); 152 153 a = new DefaultMutableTreeNode("A"); 154 root.add(a); 155 a1 = new DefaultMutableTreeNode("a1"); 156 a.add(a1); 157 158 b = new DefaultMutableTreeNode("B"); 159 root.add(b); 160 b1 = new DefaultMutableTreeNode("b1"); 161 b.add(b1); 162 b2 = new DefaultMutableTreeNode("b2"); 163 b.add(b2); 164 165 c = new DefaultMutableTreeNode("C"); 166 root.add(c); 167 c1 = new DefaultMutableTreeNode("c1"); 168 c.add(c1); 169 return new DefaultTreeModel(root); 170 } 171 172 private void expandTree(JTree tree) { 173 DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel() 174 .getRoot(); 175 Enumeration e = root.breadthFirstEnumeration(); 176 while (e.hasMoreElements()) { 177 DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement(); 178 if (node.isLeaf()) { 179 continue; 180 } 181 int row = tree.getRowForPath(new TreePath(node.getPath())); 182 tree.expandRow(row); 183 } 184 } 185 } 186 187 class TreeTransferHandler extends TransferHandler { 188 DataFlavor nodesFlavor; 189 DataFlavor[] flavors = new DataFlavor[1]; 190 DefaultMutableTreeNode[] nodesToRemove; 191 192 public TreeTransferHandler() { 193 try { 194 String mimeType = DataFlavor.javaJVMLocalObjectMimeType 195 + ";class=\"" 196 + javax.swing.tree.DefaultMutableTreeNode[].class.getName() 197 + "\""; 198 nodesFlavor = new DataFlavor(mimeType); 199 flavors[0] = nodesFlavor; 200 } catch (ClassNotFoundException e) { 201 System.out.println("ClassNotFound: " + e.getMessage()); 202 } 203 } 204 205 @Override 206 public boolean canImport(TransferHandler.TransferSupport support) { 207 if (!support.isDrop()) { 208 return false; 209 } 210 support.setShowDropLocation(true); 211 if (!support.isDataFlavorSupported(nodesFlavor)) { 212 return false; 213 } 214 // Do not allow a drop on the drag source selections. 215 JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation(); 216 JTree tree = (JTree) support.getComponent(); 217 int dropRow = tree.getRowForPath(dl.getPath()); 218 int[] selRows = tree.getSelectionRows(); 219 for (int i = 0; i < selRows.length; i++) { 220 if (selRows[i] == dropRow) { 221 return false; 222 } 223 } 224 // Do not allow MOVE-action drops if a non-leaf node is 225 // selected unless all of its children are also selected. 226 int action = support.getDropAction(); 227 if (action == MOVE) { 228 return haveCompleteNode(tree); 229 } 230 // Do not allow a non-leaf node to be copied to a level 231 // which is less than its source level. 232 TreePath dest = dl.getPath(); 233 DefaultMutableTreeNode target = (DefaultMutableTreeNode) 234 dest.getLastPathComponent(); 235 TreePath path = tree.getPathForRow(selRows[0]); 236 DefaultMutableTreeNode firstNode = (DefaultMutableTreeNode) 237 path.getLastPathComponent(); 238 if (firstNode.getChildCount() > 0 239 && target.getLevel() < firstNode.getLevel()) { 240 return false; 241 } 242 return true; 243 } 244 245 private boolean haveCompleteNode(JTree tree) { 246 int[] selRows = tree.getSelectionRows(); 247 TreePath path = tree.getPathForRow(selRows[0]); 248 DefaultMutableTreeNode first = (DefaultMutableTreeNode) 249 path.getLastPathComponent(); 250 int childCount = first.getChildCount(); 251 // first has children and no children are selected. 252 if (childCount > 0 && selRows.length == 1) { 253 return false; 254 } 255 // first may have children. 256 for (int i = 1; i < selRows.length; i++) { 257 path = tree.getPathForRow(selRows[i]); 258 DefaultMutableTreeNode next = (DefaultMutableTreeNode) 259 path.getLastPathComponent(); 260 if (first.isNodeChild(next)) { 261 // Found a child of first. 262 if (childCount > selRows.length - 1) { 263 // Not all children of first are selected. 264 return false; 265 } 266 } 267 } 268 return true; 269 } 270 271 @Override 272 protected Transferable createTransferable(JComponent c) { 273 JTree tree = (JTree) c; 274 TreePath[] paths = tree.getSelectionPaths(); 275 if (paths != null) { 276 // Make up a node array of copies for transfer and 277 // another for/of the nodes that will be removed in 278 // exportDone after a successful drop. 279 List<DefaultMutableTreeNode> copies = new ArrayList<>(); 280 List<DefaultMutableTreeNode> toRemove = new ArrayList<>(); 281 DefaultMutableTreeNode node = (DefaultMutableTreeNode) 282 paths[0].getLastPathComponent(); 283 DefaultMutableTreeNode copy = copy(node); 284 copies.add(copy); 285 toRemove.add(node); 286 for (int i = 1; i < paths.length; i++) { 287 DefaultMutableTreeNode next = (DefaultMutableTreeNode) paths[i] 288 .getLastPathComponent(); 289 // Do not allow higher level nodes to be added to list. 290 if (next.getLevel() < node.getLevel()) { 291 break; 292 } else if (next.getLevel() > node.getLevel()) { // child node 293 copy.add(copy(next)); 294 // node already contains child 295 } else { // sibling 296 copies.add(copy(next)); 297 toRemove.add(next); 298 } 299 } 300 DefaultMutableTreeNode[] nodes = copies 301 .toArray(new DefaultMutableTreeNode[copies.size()]); 302 nodesToRemove = toRemove.toArray( 303 new DefaultMutableTreeNode[toRemove.size()]); 304 return new NodesTransferable(nodes); 305 } 306 return null; 307 } 308 309 /** 310 * Defensive copy used in createTransferable. 311 */ 312 private DefaultMutableTreeNode copy(TreeNode node) { 313 return new DefaultMutableTreeNode(node); 314 } 315 316 @Override 317 protected void exportDone(JComponent source, Transferable data, int action) { 318 if ((action & MOVE) == MOVE) { 319 JTree tree = (JTree) source; 320 DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); 321 // Remove nodes saved in nodesToRemove in createTransferable. 322 for (DefaultMutableTreeNode nodesToRemove1 : nodesToRemove) { 323 model.removeNodeFromParent(nodesToRemove1); 324 } 325 } 326 } 327 328 @Override 329 public int getSourceActions(JComponent c) { 330 return COPY_OR_MOVE; 331 } 332 333 @Override 334 public boolean importData(TransferHandler.TransferSupport support) { 335 if (!canImport(support)) { 336 return false; 337 } 338 // Extract transfer data. 339 DefaultMutableTreeNode[] nodes = null; 340 try { 341 Transferable t = support.getTransferable(); 342 nodes = (DefaultMutableTreeNode[]) t.getTransferData(nodesFlavor); 343 } catch (UnsupportedFlavorException ufe) { 344 System.out.println("UnsupportedFlavor: " + ufe.getMessage()); 345 } catch (java.io.IOException ioe) { 346 System.out.println("I/O error: " + ioe.getMessage()); 347 } 348 // Get drop location info. 349 JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation(); 350 int childIndex = dl.getChildIndex(); 351 TreePath dest = dl.getPath(); 352 DefaultMutableTreeNode parent = (DefaultMutableTreeNode) 353 dest.getLastPathComponent(); 354 JTree tree = (JTree) support.getComponent(); 355 DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); 356 // Configure for drop mode. 357 int index = childIndex; // DropMode.INSERT 358 if (childIndex == -1) { // DropMode.ON 359 index = parent.getChildCount(); 360 } 361 // Add data to model. 362 for (DefaultMutableTreeNode node : nodes) { 363 model.insertNodeInto(node, parent, index++); 364 } 365 return true; 366 } 367 368 @Override 369 public String toString() { 370 return getClass().getName(); 371 } 372 373 public class NodesTransferable implements Transferable { 374 DefaultMutableTreeNode[] nodes; 375 376 public NodesTransferable(DefaultMutableTreeNode[] nodes) { 377 this.nodes = nodes; 378 } 379 380 @Override 381 public Object getTransferData(DataFlavor flavor) 382 throws UnsupportedFlavorException { 383 if (!isDataFlavorSupported(flavor)) { 384 throw new UnsupportedFlavorException(flavor); 385 } 386 return nodes; 387 } 388 389 @Override 390 public DataFlavor[] getTransferDataFlavors() { 391 return flavors; 392 } 393 394 @Override 395 public boolean isDataFlavorSupported(DataFlavor flavor) { 396 return nodesFlavor.equals(flavor); 397 } 398 } 399 }