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