1 /*
   2  * Copyright (c) 2014, 2018, 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 /*
  25  * @test
  26  * @key headful
  27  * @bug 8024061
  28  * @summary Checks that no exception is thrown if dragGestureRecognized
  29  *          takes a while to complete.
  30  * @library /test/lib
  31  * @build jdk.test.lib.Platform
  32  * @run main bug8024061
  33  */
  34 import java.awt.*;
  35 import java.awt.datatransfer.DataFlavor;
  36 import java.awt.datatransfer.Transferable;
  37 import java.awt.datatransfer.UnsupportedFlavorException;
  38 import java.awt.dnd.DnDConstants;
  39 import java.awt.dnd.DragGestureEvent;
  40 import java.awt.dnd.DragGestureListener;
  41 import java.awt.dnd.DragSource;
  42 import java.awt.dnd.DragSourceDragEvent;
  43 import java.awt.dnd.DragSourceDropEvent;
  44 import java.awt.dnd.DragSourceEvent;
  45 import java.awt.dnd.DragSourceListener;
  46 import java.awt.dnd.DropTarget;
  47 import java.awt.dnd.DropTargetDragEvent;
  48 import java.awt.dnd.DropTargetDropEvent;
  49 import java.awt.dnd.DropTargetEvent;
  50 import java.awt.dnd.DropTargetListener;
  51 import java.awt.event.InputEvent;
  52 
  53 import java.io.IOException;
  54 import java.lang.reflect.InvocationTargetException;
  55 import java.util.concurrent.CountDownLatch;
  56 import java.util.concurrent.TimeUnit;
  57 
  58 import javax.swing.*;
  59 
  60 import jdk.test.lib.Platform;
  61 
  62 /**
  63  * If dragGestureRecognized() takes a while to complete and if user performs a drag quickly,
  64  * an exception is thrown from DropTargetListener.dragEnter when it calls
  65  * DropTargetDragEvent.getTransferable().
  66  * <p>
  67  * This class introduces a delay in dragGestureRecognized() to cause the exception.
  68  */
  69 public class bug8024061 {
  70     private static final DataFlavor DropObjectFlavor;
  71     private static final int DELAY = 1000;
  72 
  73     static final DnDPanel panel1 = new DnDPanel(Color.yellow);
  74     static final DnDPanel panel2 = new DnDPanel(Color.pink);
  75     private final JFrame frame;
  76     static Point here;
  77     static Point there;
  78     static Dimension d;
  79 
  80 
  81 
  82     private static final CountDownLatch lock = new CountDownLatch(1);
  83     private static volatile Exception dragEnterException = null;
  84 
  85     static {
  86         DataFlavor flavor = null;
  87         try {
  88             flavor = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType);
  89         } catch (ClassNotFoundException e) {
  90             e.printStackTrace();
  91         }
  92         DropObjectFlavor = flavor;
  93     }
  94 
  95     bug8024061() {
  96         frame = new JFrame("DnDWithRobot");
  97         frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  98 
  99         d = new Dimension(100, 100);
 100 
 101         panel1.setPreferredSize(d);
 102         panel2.setPreferredSize(d);
 103 
 104         Container content = frame.getContentPane();
 105         content.setLayout(new GridLayout(1, 2, 5, 5));
 106         content.add(panel1);
 107         content.add(panel2);
 108 
 109         frame.pack();
 110 
 111         DropObject drop = new DropObject();
 112         drop.place(panel1, new Point(10, 10));
 113         frame.setVisible(true);
 114     }
 115 
 116     public static void main(String[] args) throws AWTException, InvocationTargetException, InterruptedException {
 117         if (!Platform.isLinux()) {
 118             System.out.println("This test is for Linux only... skipping!");
 119             return;
 120         }
 121 
 122         final bug8024061[] dnd = {null};
 123         SwingUtilities.invokeAndWait(new Runnable() {
 124             @Override
 125             public void run() {
 126                 dnd[0] = new bug8024061();
 127             }
 128         });
 129         final Robot robot = new Robot();
 130         robot.setAutoDelay(10);
 131         robot.waitForIdle();
 132         robot.delay(200);
 133 
 134         JFrame frame = dnd[0].frame;
 135         SwingUtilities.invokeAndWait(() -> {
 136             here = panel1.getLocationOnScreen();
 137             there = panel2.getLocationOnScreen();
 138         });
 139         here.translate(d.width / 2, d.height / 2);
 140         there.translate(d.width / 2, d.height / 2);
 141         robot.mouseMove(here.x, here.y);
 142         robot.mousePress(InputEvent.BUTTON1_MASK);
 143         while (here.x < there.x) {
 144             here.x += 20;
 145             robot.mouseMove(here.x, here.y);
 146             System.out.println("x = " + here.x);
 147         }
 148         robot.mouseRelease(InputEvent.BUTTON1_MASK);
 149         robot.waitForIdle();
 150         robot.mousePress(InputEvent.BUTTON1_MASK);
 151         robot.mouseRelease(InputEvent.BUTTON1_MASK);
 152         System.out.println("finished");
 153 
 154         try {
 155             if (lock.await(5, TimeUnit.SECONDS)) {
 156                 if (dragEnterException == null) {
 157                     System.out.println("Test passed.");
 158                 } else {
 159                     System.out.println("Test failed.");
 160                     dragEnterException.printStackTrace();
 161                     throw new RuntimeException(dragEnterException);
 162                 }
 163             } else {
 164                 System.out.println("Test failed. Timeout reached");
 165                 throw new RuntimeException("Timed out waiting for dragEnter()");
 166             }
 167         } finally {
 168             SwingUtilities.invokeLater(frame::dispose);
 169         }
 170     }
 171 
 172     class DropObject implements Transferable {
 173         DnDPanel panel;
 174         Color color = Color.CYAN;
 175         int width = 50;
 176         int height = 50;
 177         int x;
 178         int y;
 179 
 180         void draw(Graphics2D g) {
 181             Color savedColor = g.getColor();
 182             g.setColor(color);
 183             g.fillRect(x, y, width, height);
 184             g.setColor(Color.lightGray);
 185             g.drawRect(x, y, width, height);
 186             g.setColor(savedColor);
 187         }
 188 
 189         boolean contains(int x, int y) {
 190             return (x > this.x && x < this.x + width)
 191                     && (y > this.y && y < this.y + height);
 192         }
 193 
 194         @Override
 195         public DataFlavor[] getTransferDataFlavors() {
 196             return new DataFlavor[]{DropObjectFlavor};
 197         }
 198 
 199         void place(DnDPanel panel, Point location) {
 200             if (panel != this.panel) {
 201                 x = location.x;
 202                 y = location.y;
 203                 if (this.panel != null) {
 204                     this.panel.setDropObject(null);
 205                     this.panel.repaint();
 206                 }
 207                 this.panel = panel;
 208                 this.panel.setDropObject(this);
 209                 this.panel.repaint();
 210             }
 211         }
 212 
 213         @Override
 214         public boolean isDataFlavorSupported(DataFlavor flavor) {
 215             return DropObjectFlavor.equals(flavor);
 216         }
 217 
 218         @Override
 219         public Object getTransferData(DataFlavor flavor)
 220                 throws UnsupportedFlavorException, IOException {
 221             if (isDataFlavorSupported(flavor)) {
 222                 return this;
 223             } else {
 224                 throw new UnsupportedFlavorException(flavor);
 225             }
 226         }
 227     }
 228 
 229     static class DnDPanel extends JPanel {
 230         DropObject dropObject;
 231         final DragSource dragSource;
 232         final DropTarget dropTarget;
 233         final Color color;
 234         final DragGestureListener dgListener;
 235         final DragSourceListener dsListener;
 236         final DropTargetListener dtListener;
 237 
 238         DnDPanel(Color color) {
 239             this.color = color;
 240             this.dragSource = DragSource.getDefaultDragSource();
 241             dgListener = new DragGestureListener() {
 242                 @Override
 243                 public void dragGestureRecognized(DragGestureEvent dge) {
 244                     Point location = dge.getDragOrigin();
 245                     if (dropObject != null && dropObject.contains(location.x, location.y)) {
 246                         dragSource.startDrag(dge, DragSource.DefaultCopyNoDrop, dropObject, dsListener);
 247                         try {
 248                             Thread.sleep(DELAY);
 249                         } catch (InterruptedException e) {
 250                         }
 251                     }
 252                 }
 253             };
 254 
 255             dsListener = new DragSourceListener() {
 256                 @Override
 257                 public void dragEnter(DragSourceDragEvent dsde) {
 258                 }
 259 
 260                 @Override
 261                 public void dragOver(DragSourceDragEvent dsde) {
 262                 }
 263 
 264                 @Override
 265                 public void dropActionChanged(DragSourceDragEvent dsde) {
 266                 }
 267 
 268                 @Override
 269                 public void dragExit(DragSourceEvent dse) {
 270                 }
 271 
 272                 @Override
 273                 public void dragDropEnd(DragSourceDropEvent dsde) {
 274                 }
 275             };
 276 
 277             dtListener = new DropTargetListener() {
 278                 @Override
 279                 public void dragEnter(DropTargetDragEvent dtde) {
 280                     if (dropObject != null) {
 281                         dtde.rejectDrag();
 282                         return;
 283                     }
 284                     dtde.acceptDrag(DnDConstants.ACTION_MOVE);
 285                     try {
 286                         Transferable t = dtde.getTransferable();
 287                         Object data = t.getTransferData(DropObjectFlavor);
 288                     } catch (Exception e) {
 289                         dragEnterException = e;
 290                         e.printStackTrace();
 291                     } finally {
 292                         lock.countDown();
 293                     }
 294                 }
 295 
 296                 @Override
 297                 public void dragOver(DropTargetDragEvent dtde) {
 298                     if (dropObject != null) {
 299                         dtde.rejectDrag();
 300                         return;
 301                     }
 302                     dtde.acceptDrag(DnDConstants.ACTION_MOVE);
 303                 }
 304 
 305                 @Override
 306                 public void dropActionChanged(DropTargetDragEvent dtde) {
 307                 }
 308 
 309                 @Override
 310                 public void dragExit(DropTargetEvent dte) {
 311                 }
 312 
 313                 @Override
 314                 public void drop(DropTargetDropEvent dtde) {
 315                     if (dropObject != null) {
 316                         dtde.rejectDrop();
 317                         return;
 318                     }
 319                     try {
 320                         dtde.acceptDrop(DnDConstants.ACTION_MOVE);
 321                         Transferable t = dtde.getTransferable();
 322                         DropObject dropObject = (DropObject) t.getTransferData(DropObjectFlavor);
 323                         Point location = dtde.getLocation();
 324                         dropObject.place(DnDPanel.this, location);
 325                         dtde.dropComplete(true);
 326                     } catch (Exception e) {
 327                         e.printStackTrace();
 328                     }
 329 
 330                 }
 331             };
 332 
 333             dragSource.createDefaultDragGestureRecognizer(this,
 334                     DnDConstants.ACTION_MOVE, dgListener);
 335 
 336             dropTarget = new DropTarget(this, DnDConstants.ACTION_MOVE, dtListener, true);
 337 
 338         }
 339 
 340         public void paintComponent(Graphics g) {
 341             super.paintComponent(g);
 342             Color savedColor = g.getColor();
 343             g.setColor(color);
 344             g.fillRect(0, 0, getWidth(), getHeight());
 345             g.setColor(savedColor);
 346             if (dropObject != null) {
 347                 dropObject.draw((Graphics2D) g);
 348             }
 349         }
 350 
 351         void setDropObject(DropObject dropObject) {
 352             this.dropObject = dropObject;
 353         }
 354 
 355         DropObject findDropObject(int x, int y) {
 356             if (dropObject != null && dropObject.contains(x, y)) {
 357                 return dropObject;
 358             }
 359             return null;
 360         }
 361     }
 362 }