8027913: drop target notifications are sent out of order during DnD
authorserb
Tue, 19 Nov 2013 18:16:41 +0400
changeset 21787 11c9c9dfa450
parent 21786 2f3ad6aa2ac5
child 21788 38ffbbef9235
8027913: drop target notifications are sent out of order during DnD Reviewed-by: anthony, art
jdk/src/share/classes/java/awt/Container.java
jdk/test/java/awt/dnd/MissingDragExitEventTest/MissingDragExitEventTest.java
--- a/jdk/src/share/classes/java/awt/Container.java	Mon Nov 18 23:24:27 2013 +0400
+++ b/jdk/src/share/classes/java/awt/Container.java	Tue Nov 19 18:16:41 2013 +0400
@@ -43,7 +43,6 @@
 
 import java.security.AccessController;
 
-import java.util.Arrays;
 import java.util.EventListener;
 import java.util.HashSet;
 import java.util.Set;
@@ -4427,6 +4426,7 @@
         stopListeningForOtherDrags();
         mouseEventTarget = null;
         targetLastEntered = null;
+        targetLastEnteredDT = null;
     }
 
     /**
@@ -4617,59 +4617,80 @@
     }
 
     /*
+     * Generates dnd enter/exit events as mouse moves over lw components
+     * @param targetOver       Target mouse is over (including native container)
+     * @param e                SunDropTarget mouse event in native container
+     */
+    private void trackDropTargetEnterExit(Component targetOver, MouseEvent e) {
+        int id = e.getID();
+        if (id == MouseEvent.MOUSE_ENTERED && isMouseDTInNativeContainer) {
+            // This can happen if a lightweight component which initiated the
+            // drag has an associated drop target. MOUSE_ENTERED comes when the
+            // mouse is in the native container already. To propagate this event
+            // properly we should null out targetLastEntered.
+            targetLastEnteredDT = null;
+        } else if (id == MouseEvent.MOUSE_ENTERED) {
+            isMouseDTInNativeContainer = true;
+        } else if (id == MouseEvent.MOUSE_EXITED) {
+            isMouseDTInNativeContainer = false;
+        }
+        targetLastEnteredDT = retargetMouseEnterExit(targetOver, e,
+                                                     targetLastEnteredDT,
+                                                     isMouseDTInNativeContainer);
+    }
+
+    /*
      * Generates enter/exit events as mouse moves over lw components
      * @param targetOver        Target mouse is over (including native container)
      * @param e                 Mouse event in native container
      */
     private void trackMouseEnterExit(Component targetOver, MouseEvent e) {
-        Component       targetEnter = null;
-        int             id = e.getID();
-
-        if (e instanceof SunDropTargetEvent &&
-            id == MouseEvent.MOUSE_ENTERED &&
-            isMouseInNativeContainer == true) {
-            // This can happen if a lightweight component which initiated the
-            // drag has an associated drop target. MOUSE_ENTERED comes when the
-            // mouse is in the native container already. To propagate this event
-            // properly we should null out targetLastEntered.
-            targetLastEntered = null;
-        } else if ( id != MouseEvent.MOUSE_EXITED &&
+        if (e instanceof SunDropTargetEvent) {
+            trackDropTargetEnterExit(targetOver, e);
+            return;
+        }
+        int id = e.getID();
+
+        if ( id != MouseEvent.MOUSE_EXITED &&
              id != MouseEvent.MOUSE_DRAGGED &&
              id != LWD_MOUSE_DRAGGED_OVER &&
-             isMouseInNativeContainer == false ) {
+                !isMouseInNativeContainer) {
             // any event but an exit or drag means we're in the native container
             isMouseInNativeContainer = true;
             startListeningForOtherDrags();
-        } else if ( id == MouseEvent.MOUSE_EXITED ) {
+        } else if (id == MouseEvent.MOUSE_EXITED) {
             isMouseInNativeContainer = false;
             stopListeningForOtherDrags();
         }
-
-        if (isMouseInNativeContainer) {
-            targetEnter = targetOver;
-        }
-
-        if (targetLastEntered == targetEnter) {
-                return;
-        }
-
-        if (targetLastEntered != null) {
-            retargetMouseEvent(targetLastEntered, MouseEvent.MOUSE_EXITED, e);
+        targetLastEntered = retargetMouseEnterExit(targetOver, e,
+                                                   targetLastEntered,
+                                                   isMouseInNativeContainer);
+    }
+
+    private Component retargetMouseEnterExit(Component targetOver, MouseEvent e,
+                                             Component lastEntered,
+                                             boolean inNativeContainer) {
+        int id = e.getID();
+        Component targetEnter = inNativeContainer ? targetOver : null;
+
+        if (lastEntered != targetEnter) {
+            if (lastEntered != null) {
+                retargetMouseEvent(lastEntered, MouseEvent.MOUSE_EXITED, e);
+            }
+            if (id == MouseEvent.MOUSE_EXITED) {
+                // consume native exit event if we generate one
+                e.consume();
+            }
+
+            if (targetEnter != null) {
+                retargetMouseEvent(targetEnter, MouseEvent.MOUSE_ENTERED, e);
+            }
+            if (id == MouseEvent.MOUSE_ENTERED) {
+                // consume native enter event if we generate one
+                e.consume();
+            }
         }
-        if (id == MouseEvent.MOUSE_EXITED) {
-            // consume native exit event if we generate one
-            e.consume();
-        }
-
-        if (targetEnter != null) {
-            retargetMouseEvent(targetEnter, MouseEvent.MOUSE_ENTERED, e);
-        }
-        if (id == MouseEvent.MOUSE_ENTERED) {
-            // consume native enter event if we generate one
-            e.consume();
-        }
-
-        targetLastEntered = targetEnter;
+        return targetEnter;
     }
 
     /*
@@ -4908,21 +4929,31 @@
     private transient Component mouseEventTarget;
 
     /**
-     * The last component entered
+     * The last component entered by the {@code MouseEvent}.
      */
     private transient Component targetLastEntered;
 
     /**
+     * The last component entered by the {@code SunDropTargetEvent}.
+     */
+    private transient Component targetLastEnteredDT;
+
+    /**
      * Indicates whether {@code mouseEventTarget} was removed and nulled
      */
     private transient boolean isCleaned;
 
     /**
-     * Is the mouse over the native container
+     * Is the mouse over the native container.
      */
     private transient boolean isMouseInNativeContainer = false;
 
     /**
+     * Is DnD over the native container.
+     */
+    private transient boolean isMouseDTInNativeContainer = false;
+
+    /**
      * This variable is not used, but kept for serialization compatibility
      */
     private Cursor nativeCursor;
@@ -4960,5 +4991,8 @@
         if (targetLastEntered == removedComponent) {
             targetLastEntered = null;
         }
+        if (targetLastEnteredDT == removedComponent) {
+            targetLastEnteredDT = null;
+        }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/dnd/MissingDragExitEventTest/MissingDragExitEventTest.java	Tue Nov 19 18:16:41 2013 +0400
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8027913
+ * @library ../../regtesthelpers
+ * @build Util
+ * @compile MissingDragExitEventTest.java
+ * @run main/othervm MissingDragExitEventTest
+ * @author Sergey Bylokhov
+ */
+
+import java.awt.Color;
+import java.awt.Point;
+import java.awt.Robot;
+import java.awt.Toolkit;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JFrame;
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+
+import sun.awt.SunToolkit;
+import test.java.awt.regtesthelpers.Util;
+
+public class MissingDragExitEventTest {
+
+    private static volatile JFrame frame;
+    private static boolean FAILED;
+    private static boolean MOUSE_ENTERED_DT;
+    private static boolean MOUSE_ENTERED;
+    private static boolean MOUSE_EXIT_TD;
+    private static boolean MOUSE_EXIT;
+    private static int SIZE = 300;
+
+    private static void initAndShowUI() {
+        frame = new JFrame("Test frame");
+
+        frame.setSize(SIZE, SIZE);
+        frame.setLocationRelativeTo(null);
+        final JTextArea jta = new JTextArea();
+        jta.setBackground(Color.RED);
+        frame.add(jta);
+        jta.setText("1234567890");
+        jta.setFont(jta.getFont().deriveFont(150f));
+        jta.setDragEnabled(true);
+        jta.selectAll();
+        jta.setDropTarget(new DropTarget(jta, DnDConstants.ACTION_COPY,
+                                         new TestdropTargetListener()));
+        jta.addMouseListener(new TestMouseAdapter());
+        frame.setVisible(true);
+    }
+
+    public static void main(final String[] args) throws Exception {
+        try {
+            final Robot r = new Robot();
+            r.setAutoDelay(50);
+            r.mouseMove(100, 100);
+            Util.waitForIdle(r);
+
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    initAndShowUI();
+                }
+            });
+
+            final Point inside = new Point(frame.getLocationOnScreen());
+            inside.translate(20, SIZE / 2);
+            final Point outer = new Point(inside);
+            outer.translate(-40, 0);
+            r.mouseMove(inside.x, inside.y);
+            r.mousePress(InputEvent.BUTTON1_MASK);
+            try {
+                for (int i = 0; i < 3; ++i) {
+                    Util.mouseMove(r, inside, outer);
+                    Util.mouseMove(r, outer, inside);
+                }
+            } finally {
+                r.mouseRelease(InputEvent.BUTTON1_MASK);
+            }
+            sleep();
+
+            if (FAILED || !MOUSE_ENTERED || !MOUSE_ENTERED_DT || !MOUSE_EXIT
+                    || !MOUSE_EXIT_TD) {
+                throw new RuntimeException("Failed");
+            }
+        } finally {
+            if (frame != null) {
+                frame.dispose();
+            }
+        }
+    }
+
+    private static void sleep() {
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        ((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
+    }
+
+    static class TestdropTargetListener extends DropTargetAdapter {
+
+        private volatile boolean inside;
+
+        @Override
+        public void dragEnter(final DropTargetDragEvent dtde) {
+            if (inside) {
+                FAILED = true;
+                Thread.dumpStack();
+            }
+            inside = true;
+            MOUSE_ENTERED_DT = true;
+            try {
+                Thread.sleep(10000); // we should have time to leave a component
+            } catch (InterruptedException ignored) {
+            }
+        }
+
+        @Override
+        public void dragOver(final DropTargetDragEvent dtde) {
+            if (!inside) {
+                FAILED = true;
+                Thread.dumpStack();
+            }
+        }
+
+        @Override
+        public void dragExit(final DropTargetEvent dte) {
+            if (!inside) {
+                FAILED = true;
+                Thread.dumpStack();
+            }
+            inside = false;
+            MOUSE_EXIT_TD = true;
+        }
+
+        @Override
+        public void drop(final DropTargetDropEvent dtde) {
+            if (!inside) {
+                FAILED = true;
+                Thread.dumpStack();
+            }
+            inside = false;
+        }
+    }
+
+    static class TestMouseAdapter extends MouseAdapter {
+
+        private volatile boolean inside;
+
+        @Override
+        public void mouseEntered(final MouseEvent e) {
+            if (inside) {
+                FAILED = true;
+                Thread.dumpStack();
+            }
+            inside = true;
+            MOUSE_ENTERED = true;
+        }
+
+        @Override
+        public void mouseExited(final MouseEvent e) {
+            if (!inside) {
+                FAILED = true;
+                Thread.dumpStack();
+            }
+            inside = false;
+            MOUSE_EXIT = true;
+        }
+    }
+}