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 
33 import java.awt.Dimension;
34 import java.awt.GraphicsConfiguration;
35 import java.awt.GraphicsDevice;
36 import java.awt.GraphicsEnvironment;
37 import java.awt.Point;
38 import java.awt.Rectangle;
39 import java.awt.Toolkit;
40 import java.awt.event.ComponentAdapter;
41 import java.awt.event.ComponentEvent;
42 import java.awt.event.KeyEvent;
43 import java.util.concurrent.CountDownLatch;
44 
45 import javax.swing.JFrame;
46 import javax.swing.JMenu;
47 import javax.swing.JMenuBar;
48 import javax.swing.JMenuItem;
49 import javax.swing.JPopupMenu;
50 import javax.swing.SwingUtilities;
51 import javax.swing.UIManager;
52 
53 public class bug8071705 {
54 
main(String[] args)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 
checkScreens()92     private static GraphicsDevice[] checkScreens() {
93         GraphicsEnvironment ge =
94             GraphicsEnvironment.getLocalGraphicsEnvironment();
95         return ge.getScreenDevices();
96     }
97 
createGUI()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 
checkConfigs(GraphicsDevice[] devices)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;
FrameListener(GraphicsDevice device, CountDownLatch latch, 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
componentShown(ComponentEvent e)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 
setLocation(JFrame frame, GraphicsDevice device)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 
runActualTest(GraphicsDevice device, CountDownLatch latch, JFrame frame, boolean [] result)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