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