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