changeset 16896:b38931a57a60

8176448: [macos] Popups in JCombobox and Choice have incorrect location in multiscreen systems Reviewed-by: alexsch, azvegint
author serb
date Wed, 15 Mar 2017 18:56:21 +0300
parents 23f609916fba
children 3556f4cd047b
files src/java.desktop/macosx/classes/com/apple/laf/AquaComboBoxPopup.java test/java/awt/Choice/ChoicePopupLocation/ChoicePopupLocation.java test/javax/swing/plaf/basic/BasicComboPopup/7072653/bug7072653.java test/javax/swing/plaf/basic/BasicComboPopup/JComboBoxPopupLocation/JComboBoxPopupLocation.java
diffstat 4 files changed, 377 insertions(+), 78 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.desktop/macosx/classes/com/apple/laf/AquaComboBoxPopup.java	Tue Mar 14 18:35:32 2017 +0300
+++ b/src/java.desktop/macosx/classes/com/apple/laf/AquaComboBoxPopup.java	Wed Mar 15 18:56:21 2017 +0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2017, 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
@@ -25,18 +25,29 @@
 
 package com.apple.laf;
 
-import java.awt.*;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
 import java.awt.Insets;
+import java.awt.Point;
 import java.awt.Rectangle;
-import java.awt.event.*;
+import java.awt.Toolkit;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
 
-import javax.swing.*;
+import javax.swing.Box;
+import javax.swing.JComboBox;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingUtilities;
 import javax.swing.plaf.basic.BasicComboPopup;
 
 import sun.lwawt.macosx.CPlatformWindow;
 
 @SuppressWarnings("serial") // Superclass is not serializable across versions
-class AquaComboBoxPopup extends BasicComboPopup {
+final class AquaComboBoxPopup extends BasicComboPopup {
     static final int FOCUS_RING_PAD_LEFT = 6;
     static final int FOCUS_RING_PAD_RIGHT = 6;
     static final int FOCUS_RING_PAD_BOTTOM = 5;
@@ -201,9 +212,6 @@
         //System.err.println("GetBestScreenBounds p: "+ p.x + ", " + p.y);
         final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
         final GraphicsDevice[] gs = ge.getScreenDevices();
-        //System.err.println("  gs.length = " + gs.length);
-        final Rectangle comboBoxBounds = comboBox.getBounds();
-
         for (final GraphicsDevice gd : gs) {
             final GraphicsConfiguration[] gc = gd.getConfigurations();
             for (final GraphicsConfiguration element0 : gc) {
@@ -215,15 +223,14 @@
         }
 
         // Hmm.  Origin's off screen, but is any part on?
+        final Rectangle comboBoxBounds = comboBox.getBounds();
         comboBoxBounds.setLocation(p);
         for (final GraphicsDevice gd : gs) {
             final GraphicsConfiguration[] gc = gd.getConfigurations();
             for (final GraphicsConfiguration element0 : gc) {
                 final Rectangle gcBounds = element0.getBounds();
                 if (gcBounds.intersects(comboBoxBounds)) {
-                    if (gcBounds.contains(p)) {
-                        return getAvailableScreenArea(gcBounds, element0);
-                    }
+                    return getAvailableScreenArea(gcBounds, element0);
                 }
             }
         }
@@ -234,8 +241,15 @@
     private Rectangle getAvailableScreenArea(Rectangle bounds,
                                              GraphicsConfiguration gc) {
         Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
-        return new Rectangle(0, insets.top, bounds.width,
-                bounds.height - insets.top);
+        return new Rectangle(bounds.x + insets.left, bounds.y + insets.top,
+                             bounds.width - insets.left - insets.right,
+                             bounds.height - insets.top - insets.bottom);
+    }
+
+    private int getComboBoxEdge(int py, boolean bottom) {
+        int offset = bottom ? 9 : -9;
+        // if py is less than new y we have a clipped combo, so leave it alone.
+        return Math.min((py / 2) + offset, py);
     }
 
     @Override
@@ -246,7 +260,7 @@
         if (isPopdown && !isTableCellEditor) {
             // place the popup just below the button, which is
             // near the center of a large combo box
-            py = Math.min((py / 2) + 9, py); // if py is less than new y we have a clipped combo, so leave it alone.
+            py = getComboBoxEdge(py, true);
         }
 
         // px & py are relative to the combo box
@@ -291,8 +305,12 @@
         // Make sure it's all on the screen - shift it by the amount it's off
         p.x += px;
         p.y += py; // Screen location of px & py
-        if (p.x < scrBounds.x) px -= (p.x + scrBounds.x);
-        if (p.y < scrBounds.y) py -= (p.y + scrBounds.y);
+        if (p.x < scrBounds.x) {
+            px = px + (scrBounds.x - p.x);
+        }
+        if (p.y < scrBounds.y) {
+            py = py + (scrBounds.y - p.y);
+        }
 
         final Point top = new Point(0, 0);
         SwingUtilities.convertPointFromScreen(top, comboBox);
@@ -324,22 +342,27 @@
         }
 
         final Rectangle r = new Rectangle(px, py, pw, ph);
-        if (py + ph > scrBounds.y + scrBounds.height) {
-            if (ph <= -scrBounds.y ) {
-                // popup goes above
-                r.y = -ph ;
-            } else {
-                // a full screen height popup
-                r.y = scrBounds.y + Math.max(0, (scrBounds.height - ph) / 2 );
-                r.height = Math.min(scrBounds.height, ph);
-            }
+        if (r.y + r.height < top.y + scrBounds.y + scrBounds.height) {
+            return r;
+        }
+        // Check whether it goes below the bottom of the screen, if so flip it
+        int newY = getComboBoxEdge(comboBoxBounds.height, false) - ph - comboBoxInsets.top;
+        if (newY > top.y + scrBounds.y) {
+            return new Rectangle(px, newY, r.width, r.height);
+        } else {
+            // There are no place at top, move popup to the center of the screen
+            r.y = top.y + scrBounds.y + Math.max(0, (scrBounds.height - ph) / 2 );
+            r.height = Math.min(scrBounds.height, ph);
         }
         return r;
     }
 
     // The one to use when itemCount <= maxRowCount.  Size never adjusts for arrows
     // We want it positioned so the selected item is right above the combo box
-    protected Rectangle computePopupBoundsForMenu(final int px, final int py, final int pw, final int ph, final int itemCount, final Rectangle scrBounds) {
+    protected Rectangle computePopupBoundsForMenu(final int px, final int py,
+                                                  final int pw, final int ph,
+                                                  final int itemCount,
+                                                  final Rectangle scrBounds) {
         //System.err.println("computePopupBoundsForMenu: " + px + "," + py + " " +  pw + "," + ph);
         //System.err.println("itemCount: " +itemCount +" src: "+ scrBounds);
         int elementSize = 0; //kDefaultItemSize;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/awt/Choice/ChoicePopupLocation/ChoicePopupLocation.java	Wed Mar 15 18:56:21 2017 +0300
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2017, 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.
+ */
+
+import java.awt.Choice;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Robot;
+import java.awt.Toolkit;
+import java.awt.event.InputEvent;
+
+/**
+ * @test
+ * @bug 8176448
+ * @run main/timeout=300 ChoicePopupLocation
+ */
+public final class ChoicePopupLocation {
+
+    private static final int SIZE = 350;
+
+    public static void main(final String[] args) throws Exception {
+        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+        GraphicsDevice[] sds = ge.getScreenDevices();
+        Point left = null;
+        for (GraphicsDevice sd : sds) {
+            GraphicsConfiguration gc = sd.getDefaultConfiguration();
+            Rectangle bounds = gc.getBounds();
+            if (left == null || left.x > bounds.x) {
+                left = new Point(bounds.x, bounds.y + bounds.height / 2);
+            }
+
+            Point point = new Point(bounds.x, bounds.y);
+            Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
+            while (point.y < bounds.y + bounds.height - insets.bottom - SIZE ) {
+                while (point.x < bounds.x + bounds.width - insets.right - SIZE) {
+                    test(point);
+                    point.translate(bounds.width / 5, 0);
+                }
+                point.setLocation(bounds.x, point.y + bounds.height / 5);
+            }
+
+        }
+        if (left != null) {
+            left.translate(-50, 0);
+            test(left);
+        }
+    }
+
+    private static void test(final Point tmp) throws Exception {
+        Choice choice = new Choice();
+        for (int i = 1; i < 7; i++) {
+            choice.add("Long-long-long-long-long text in the item-" + i);
+        }
+        Frame frame = new Frame();
+        try {
+            frame.setAlwaysOnTop(true);
+            frame.setLayout(new FlowLayout());
+            frame.add(choice);
+            frame.pack();
+            frame.setSize(frame.getWidth(), SIZE);
+            frame.setVisible(true);
+            frame.setLocation(tmp.x, tmp.y);
+            openPopup(choice);
+        } finally {
+            frame.dispose();
+        }
+    }
+
+    private static void openPopup(final Choice choice) throws Exception {
+        Robot robot = new Robot();
+        robot.setAutoDelay(100);
+        robot.setAutoWaitForIdle(true);
+        robot.waitForIdle();
+        Point pt = choice.getLocationOnScreen();
+        robot.mouseMove(pt.x + choice.getWidth() / 2,
+                        pt.y + choice.getHeight() / 2);
+        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
+        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
+        int x = pt.x + choice.getWidth() / 2;
+        int y = pt.y + choice.getHeight() / 2 + 70;
+        robot.mouseMove(x, y);
+        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
+        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
+        robot.waitForIdle();
+        if (choice.getSelectedIndex() == 0) {
+            throw new RuntimeException();
+        }
+    }
+}
--- a/test/javax/swing/plaf/basic/BasicComboPopup/7072653/bug7072653.java	Tue Mar 14 18:35:32 2017 +0300
+++ b/test/javax/swing/plaf/basic/BasicComboPopup/7072653/bug7072653.java	Wed Mar 15 18:56:21 2017 +0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2017, 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
@@ -24,77 +24,78 @@
 /*
  * @test
  * @key headful
- * @bug 7072653 8144161
+ * @bug 7072653 8144161 8176448
  * @summary JComboBox popup mispositioned if its height exceeds the screen height
  * @run main bug7072653
  */
+
 import java.awt.FlowLayout;
 import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
 import java.awt.Insets;
+import java.awt.Rectangle;
 import java.awt.Robot;
-import javax.swing.event.PopupMenuEvent;
-import javax.swing.event.PopupMenuListener;
 import java.awt.Toolkit;
 import java.awt.Window;
-import java.util.Arrays;
+
 import javax.swing.DefaultComboBoxModel;
 import javax.swing.JComboBox;
 import javax.swing.JFrame;
 import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
 import javax.swing.UnsupportedLookAndFeelException;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
 
 public class bug7072653 {
 
     private static JComboBox combobox;
     private static JFrame frame;
     private static Robot robot;
-    private static volatile String errorString = "";
 
     public static void main(String[] args) throws Exception {
         robot = new Robot();
-        robot.delay(100);
-        UIManager.LookAndFeelInfo[] lookAndFeelArray
-                = UIManager.getInstalledLookAndFeels();
-        for (UIManager.LookAndFeelInfo lookAndFeelItem : lookAndFeelArray) {
-            executeCase(lookAndFeelItem.getClassName());
-            robot.delay(1000);
-        }
-        if (!"".equals(errorString)) {
-
-            throw new RuntimeException("Error Log:\n" + errorString);
+        GraphicsEnvironment ge =
+                GraphicsEnvironment.getLocalGraphicsEnvironment();
+        UIManager.LookAndFeelInfo[] lookAndFeelArray =
+                UIManager.getInstalledLookAndFeels();
+        for (GraphicsDevice sd : ge.getScreenDevices()) {
+            for (UIManager.LookAndFeelInfo lookAndFeelItem : lookAndFeelArray) {
+                executeCase(lookAndFeelItem.getClassName(), sd);
+                robot.waitForIdle();
+            }
         }
     }
 
-    private static void executeCase(String lookAndFeelString) throws Exception {
+    private static void executeCase(String lookAndFeelString, GraphicsDevice sd)
+            throws Exception {
         if (tryLookAndFeel(lookAndFeelString)) {
-            SwingUtilities.invokeAndWait(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        setup(lookAndFeelString);
-                        test();
-                    } catch (Exception ex) {
-                        errorString += "\n";
-                        errorString += Arrays.toString(ex.getStackTrace());
-                    }
-                    finally {
-                        frame.dispose();
-                    }
+            SwingUtilities.invokeAndWait(() -> {
+                try {
+                    setup(lookAndFeelString, sd);
+                    test();
+                } catch (Exception ex) {
+                    throw new RuntimeException(ex);
+                } finally {
+                    frame.dispose();
                 }
             });
         }
-
     }
 
-    private static void setup(String lookAndFeelString)
+    private static void setup(String lookAndFeelString, GraphicsDevice sd)
             throws Exception {
+        GraphicsConfiguration gc = sd.getDefaultConfiguration();
+        Rectangle gcBounds = gc.getBounds();
+        frame = new JFrame("JComboBox Test " + lookAndFeelString, gc);
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        frame.setSize(400, 200);
+        frame.getContentPane().setLayout(new FlowLayout());
+        frame.setLocation(
+                gcBounds.x + gcBounds.width / 2 - frame.getWidth() / 2,
+                gcBounds.y + gcBounds.height / 2 - frame.getHeight() / 2);
 
-        frame = new JFrame("JComboBox Test " + lookAndFeelString);
-        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-        frame.setSize(320, 200);
-        frame.getContentPane().setLayout(new FlowLayout());
-        frame.setLocationRelativeTo(null);
         combobox = new JComboBox(new DefaultComboBoxModel() {
             @Override
             public Object getElementAt(int index) {
@@ -108,6 +109,7 @@
         });
 
         combobox.setMaximumRowCount(100);
+        combobox.putClientProperty("JComboBox.isPopDown", true);
         frame.getContentPane().add(combobox);
         frame.setVisible(true);
         combobox.addPopupMenuListener(new PopupMenuListener() {
@@ -120,30 +122,24 @@
                 int height = 0;
                 for (Window window : JFrame.getWindows()) {
                     if (Window.Type.POPUP == window.getType()) {
-                        height = window.getSize().height;
-                        break;
+                        if (window.getOwner().isVisible()) {
+                            height = window.getSize().height;
+                            break;
+                        }
                     }
                 }
-                GraphicsConfiguration gc
-                        = combobox.getGraphicsConfiguration();
-                Insets screenInsets = Toolkit.getDefaultToolkit()
-                        .getScreenInsets(gc);
+                GraphicsConfiguration gc = combobox.getGraphicsConfiguration();
+                Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
                 int gcHeight = gc.getBounds().height;
-                if (lookAndFeelString.contains("aqua")) {
-                    gcHeight = gcHeight - screenInsets.top;
-                    //For Aqua LAF
-                } else {
-                    gcHeight = gcHeight - screenInsets.top
-                            - screenInsets.bottom;
-                }
+                gcHeight = gcHeight - insets.top - insets.bottom;
                 if (height == gcHeight) {
                     return;
                 }
 
                 String exception = "Popup window height "
                         + "For LookAndFeel" + lookAndFeelString + " is wrong"
-                        + "\nShould be " + height + "Actually " + gcHeight;
-                errorString += exception;
+                        + "\nShould be " + gcHeight + ", Actually " + height;
+                throw new RuntimeException(exception);
             }
 
             @Override
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/swing/plaf/basic/BasicComboPopup/JComboBoxPopupLocation/JComboBoxPopupLocation.java	Wed Mar 15 18:56:21 2017 +0300
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2017, 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.
+ */
+
+import java.awt.EventQueue;
+import java.awt.FlowLayout;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Robot;
+import java.awt.Toolkit;
+import java.awt.event.InputEvent;
+
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.UIManager;
+
+/**
+ * @test
+ * @bug 8176448
+ * @run main/timeout=600 JComboBoxPopupLocation
+ */
+public final class JComboBoxPopupLocation {
+
+    private static final int SIZE = 300;
+    public static final String PROPERTY_NAME = "JComboBox.isPopDown";
+    private static volatile Robot robot;
+    private static volatile JComboBox<String> comboBox;
+    private static volatile JFrame frame;
+
+    public static void main(final String[] args) throws Exception {
+        robot = new Robot();
+        robot.setAutoDelay(100);
+        robot.setAutoWaitForIdle(true);
+        GraphicsEnvironment ge =
+                GraphicsEnvironment.getLocalGraphicsEnvironment();
+        GraphicsDevice[] sds = ge.getScreenDevices();
+        UIManager.LookAndFeelInfo[] lookAndFeelArray =
+                UIManager.getInstalledLookAndFeels();
+        for (UIManager.LookAndFeelInfo lookAndFeelItem : lookAndFeelArray) {
+            System.setProperty(PROPERTY_NAME, "true");
+            step(sds, lookAndFeelItem);
+            if (lookAndFeelItem.getClassName().contains("Aqua")) {
+                System.setProperty(PROPERTY_NAME, "false");
+                step(sds, lookAndFeelItem);
+            }
+        }
+    }
+
+    private static void step(GraphicsDevice[] sds,
+                             UIManager.LookAndFeelInfo lookAndFeelItem)
+            throws Exception {
+        UIManager.setLookAndFeel(lookAndFeelItem.getClassName());
+        Point left = null;
+        for (final GraphicsDevice sd : sds) {
+            GraphicsConfiguration gc = sd.getDefaultConfiguration();
+            Rectangle bounds = gc.getBounds();
+            if (left == null || left.x > bounds.x) {
+                left = new Point(bounds.x, bounds.y + bounds.height / 2);
+            }
+            Point point = new Point(bounds.x, bounds.y);
+            Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
+            while (point.y < bounds.y + bounds.height - insets.bottom - SIZE ) {
+                while (point.x < bounds.x + bounds.width - insets.right - SIZE) {
+                    try {
+                        EventQueue.invokeAndWait(() -> {
+                            setup(point);
+                        });
+                        robot.waitForIdle();
+                        test(comboBox);
+                        robot.waitForIdle();
+                        validate(comboBox);
+                        robot.waitForIdle();
+                        point.translate(bounds.width / 5, 0);
+                    } finally {
+                        dispose();
+                    }
+                }
+                point.setLocation(bounds.x, point.y + bounds.height / 5);
+            }
+        }
+        if (left != null) {
+            final Point finalLeft = left;
+            finalLeft.translate(-50, 0);
+            try {
+                EventQueue.invokeAndWait(() -> {
+                    setup(finalLeft);
+                });
+                robot.waitForIdle();
+                test(comboBox);
+                robot.waitForIdle();
+                validate(comboBox);
+            } finally {
+                dispose();
+            }
+        }
+    }
+
+    private static void dispose() throws Exception {
+        EventQueue.invokeAndWait(() -> {
+            if (frame != null) {
+                frame.dispose();
+            }
+        });
+    }
+
+    private static void setup(final Point tmp) {
+        comboBox = new JComboBox<>();
+        for (int i = 1; i < 7; i++) {
+            comboBox.addItem("Long-long-long-long-long text in the item-" + i);
+        }
+        String property = System.getProperty(PROPERTY_NAME);
+        comboBox.putClientProperty(PROPERTY_NAME, Boolean.valueOf(property));
+        frame = new JFrame();
+        frame.setAlwaysOnTop(true);
+        frame.setLayout(new FlowLayout());
+        frame.add(comboBox);
+        frame.pack();
+        frame.setSize(frame.getWidth(), SIZE);
+        frame.setVisible(true);
+        frame.setLocation(tmp.x, tmp.y);
+    }
+
+    private static void test(final JComboBox comboBox) throws Exception {
+        Point pt = comboBox.getLocationOnScreen();
+        robot.mouseMove(pt.x + comboBox.getWidth() / 2,
+                        pt.y + comboBox.getHeight() / 2);
+        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
+        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
+        int x = pt.x + comboBox.getWidth() / 2;
+        int y = pt.y + comboBox.getHeight() / 2 + 70;
+        robot.mouseMove(x, y);
+        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
+        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
+    }
+
+    private static void validate(final JComboBox comboBox) throws Exception {
+        EventQueue.invokeAndWait(() -> {
+            if (comboBox.getSelectedIndex() == 0) {
+                throw new RuntimeException();
+            }
+        });
+    }
+}