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/*
25 * @test
26 * @key headful
27 * @bug 8071705
28 * @summary  Java application menu misbehaves when running multiple screen stacked vertically
29 * @build bug8071705
30 * @run main/othervm bug8071705
31 */
32
33import java.awt.Dimension;
34import java.awt.GraphicsConfiguration;
35import java.awt.GraphicsDevice;
36import java.awt.GraphicsEnvironment;
37import java.awt.Point;
38import java.awt.Rectangle;
39import java.awt.Toolkit;
40import java.awt.event.ComponentAdapter;
41import java.awt.event.ComponentEvent;
42import java.awt.event.KeyEvent;
43import java.util.concurrent.CountDownLatch;
44
45import javax.swing.JFrame;
46import javax.swing.JMenu;
47import javax.swing.JMenuBar;
48import javax.swing.JMenuItem;
49import javax.swing.JPopupMenu;
50import javax.swing.SwingUtilities;
51import javax.swing.UIManager;
52
53public class bug8071705 {
54
55    public static void main(String[] args) throws Exception {
56
57        final CountDownLatch latch = new CountDownLatch(1);
58        final boolean [] result = new boolean[1];
59
60        SwingUtilities.invokeLater(new Runnable() {
61            @Override
62            public void run() {
63                JFrame frame = createGUI();
64                GraphicsDevice[] devices = checkScreens();
65
66                // check if we have more than one and if they are stacked
67                // vertically
68                GraphicsDevice device = checkConfigs(devices);
69                if (device == null) {
70                    // just pass the test
71                    frame.dispose();
72                    result[0] = true;
73                    latch.countDown();
74                } else {
75                    FrameListener listener =
76                            new FrameListener(device, latch, result);
77                    frame.addComponentListener(listener);
78                    frame.setVisible(true);
79                }
80            }
81        });
82
83        latch.await();
84
85        if (result[0] == false) {
86            throw new RuntimeException("popup menu rendered in wrong position");
87        }
88
89        System.out.println("OK");
90    }
91
92    private static GraphicsDevice[] checkScreens() {
93        GraphicsEnvironment ge =
94            GraphicsEnvironment.getLocalGraphicsEnvironment();
95        return ge.getScreenDevices();
96    }
97
98    private static JFrame createGUI() {
99        JMenuBar menuBar = new JMenuBar();
100        JMenu menu = new JMenu("Some menu");
101        menuBar.add(menu);
102
103        for (int i = 0; i < 10; i++) {
104            menu.add(new JMenuItem("Some menu #" + i));
105        }
106
107        JFrame frame = new JFrame();
108        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
109        frame.setMinimumSize(new Dimension(200, 200));
110        frame.setJMenuBar(menuBar);
111        return frame;
112    }
113
114    private static GraphicsDevice checkConfigs(GraphicsDevice[] devices) {
115
116        GraphicsDevice correctDevice = null;
117        if (devices.length < 2) {
118            return correctDevice;
119        }
120
121        Toolkit toolkit = Toolkit.getDefaultToolkit();
122        Rectangle screenBounds = new Rectangle(toolkit.getScreenSize());
123        int halfScreen = screenBounds.height/2;
124
125        for(int i = 0; i < devices.length; i++) {
126            if(devices[i].getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
127                GraphicsConfiguration conf =
128                        devices[i].getDefaultConfiguration();
129                Rectangle bounds = conf.getBounds();
130                if (bounds.y >= halfScreen) {
131                    // found
132                    correctDevice = devices[i];
133                    break;
134                }
135            }
136        }
137        return correctDevice;
138    }
139
140    private static class FrameListener extends ComponentAdapter {
141
142        private GraphicsDevice device;
143        private CountDownLatch latch;
144        private boolean [] result;
145        public FrameListener(GraphicsDevice device,
146                             CountDownLatch latch,
147                             boolean [] result)
148        {
149            this.device = device;
150            this.latch = latch;
151            this.result = result;
152        }
153
154        @Override
155        public void componentShown(ComponentEvent e) {
156            JFrame frame = (JFrame) e.getComponent();
157
158            runActualTest(device, latch, frame, result);
159
160            frame.setVisible(false);
161            frame.dispose();
162            latch.countDown();
163        }
164    }
165
166    private static Rectangle setLocation(JFrame frame, GraphicsDevice device) {
167        GraphicsConfiguration conf = device.getDefaultConfiguration();
168        Rectangle bounds = conf.getBounds();
169
170        // put just below half screen
171        int x = bounds.x + bounds.width/2;
172        int y = bounds.y + bounds.height/2;
173        frame.setLocation(x, y);
174
175        return bounds;
176    }
177
178    private static void runActualTest(GraphicsDevice device,
179                                      CountDownLatch latch,
180                                      JFrame frame,
181                                      boolean [] result)
182    {
183        Rectangle screenBounds = setLocation(frame, device);
184        JMenu menu = frame.getJMenuBar().getMenu(0);
185        menu.doClick();
186
187        Point location = menu.getLocationOnScreen();
188        JPopupMenu pm = menu.getPopupMenu();
189        Dimension pmSize = pm.getSize();
190
191        int yOffset = UIManager.getInt("Menu.submenuPopupOffsetY");
192        int height = location.y + yOffset + pmSize.height + menu.getHeight();
193        int available = screenBounds.y + screenBounds.height - height;
194        if (available > 0) {
195            Point origin = pm.getLocationOnScreen();
196            if (origin.y < location.y) {
197                // growing upward, wrong!
198                result[0] = false;
199            } else {
200                // growing downward, ok!
201                result[0] = true;
202            }
203        } else {
204            // there is no space, growing upward would be ok, so we pass
205            result[0] = true;
206        }
207    }
208}
209