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