1 /*
2  * Copyright (c) 2018, 2021, 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.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 /**
27  * @test
28  * @key headful
29  * @bug 8194327
30  * @summary [macosx] AWT windows have incorrect main/key window behaviors
31  * @author Alan Snyder
32  * @library /test/lib
33  * @run main/othervm/native TestMainKeyWindow
34  * @requires (os.family == "mac")
35  */
36 
37 import java.awt.*;
38 import java.awt.event.ActionEvent;
39 import java.awt.event.InputEvent;
40 import java.awt.event.KeyEvent;
41 import java.io.IOException;
42 import java.lang.reflect.InvocationTargetException;
43 import java.util.Objects;
44 import javax.swing.*;
45 
46 import jdk.test.lib.process.ProcessTools;
47 
48 public class TestMainKeyWindow
49 {
50     static TestMainKeyWindow theTest;
51 
52     KeyStroke commandT = KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.META_DOWN_MASK);
53 
54     int nextX = 130;
55 
56     private final MyFrame frame1;
57     private final MyFrame frame2;
58     private final Object COLOR_PANEL = "Color Panel";
59     private final Object NATIVE_WINDOW = "Native Window";
60 
61     // these bounds must agree with the native code that creates the windows
62     private Rectangle colorPanelBounds = new Rectangle(130, 300, 225, 400);  // approximate is OK
63     private Rectangle nativeWindowBounds = new Rectangle(130, 200, 200, 100);
64 
65     private Robot robot;
66 
67     private int actionCounter;
68     private Object actionTarget;
69 
70     private int failureCount;
71     private Process process;
72 
TestMainKeyWindow()73     public TestMainKeyWindow()
74     {
75         System.loadLibrary("testMainKeyWindow");
76 
77         JMenuBar defaultMenuBar = createMenuBar("Application", true);
78         Desktop.getDesktop().setDefaultMenuBar(defaultMenuBar);
79 
80         setup();
81 
82         frame1 = new MyFrame("Frame 1");
83         frame2 = new MyFrame("Frame 2");
84         frame1.setVisible(true);
85         frame2.setVisible(true);
86 
87         try {
88             robot = new Robot();
89             robot.setAutoDelay(150);
90         } catch (AWTException ex) {
91             throw new RuntimeException(ex);
92         }
93     }
94 
95     class MyFrame
96         extends JFrame
97     {
MyFrame(String title)98         public MyFrame(String title)
99             throws HeadlessException
100         {
101             super(title);
102 
103             JMenuBar mainMenuBar = createMenuBar(title, true);
104             setJMenuBar(mainMenuBar);
105             setBounds(nextX, 60, 200, 90);
106             nextX += 250;
107             JComponent contentPane = new JPanel();
108             setContentPane(contentPane);
109             contentPane.setLayout(new FlowLayout());
110             contentPane.add(new JCheckBox("foo", true));
111             InputMap inputMap = contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
112             inputMap.put(commandT, "test");
113             ActionMap actionMap = contentPane.getActionMap();
114             actionMap.put("test", new MyAction(title + " Key"));
115         }
116     }
117 
runTest()118     private void runTest()
119     {
120         failureCount = 0;
121         robot.waitForIdle();
122         performTest(frame1, false);
123         performTest(frame1, true);
124         performTest(frame2, false);
125         performTest(frame2, true);
126         performTest(NATIVE_WINDOW, false);
127         performTest(NATIVE_WINDOW, true);
128         performTest(COLOR_PANEL, false);
129         if (failureCount > 0) {
130             throw new RuntimeException("Test failed: " + failureCount + " failure(s)");
131         }
132     }
133 
performTest(Object windowIdentification, boolean selectColorPanel)134     private void performTest(Object windowIdentification, boolean selectColorPanel)
135     {
136         setupWindows(windowIdentification, selectColorPanel);
137 
138         performMenuShortcutTest(windowIdentification, selectColorPanel);
139         performMenuItemTest(windowIdentification, selectColorPanel);
140 
141         // test deactivating and reactivating the application
142         // the window state and behavior should be restored
143 
144         openOtherApplication();
145         activateApplication();
146         robot.delay(1000);
147 
148         performMenuShortcutTest(windowIdentification, selectColorPanel);
149         performMenuItemTest(windowIdentification, selectColorPanel);
150     }
151 
execute()152     private Process execute() {
153         try {
154             ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
155                     TestMainKeyWindow.class.getSimpleName(), "mark");
156             return ProcessTools.startProcess("Other frame", pb);
157         } catch (IOException ex) {
158             throw new RuntimeException("Unable to execute command");
159         }
160     }
161 
openOtherApplication()162     private void openOtherApplication() {
163         if (process != null) {
164             process.destroyForcibly();
165         }
166         process = execute();
167         robot.delay(1000);
168     }
169 
performMenuShortcutTest(Object windowIdentification, boolean selectColorPanel)170     private void performMenuShortcutTest(Object windowIdentification, boolean selectColorPanel)
171     {
172         int currentActionCount = actionCounter;
173 
174         // Perform the menu shortcut
175         robot.keyPress(KeyEvent.VK_META);
176         robot.keyPress(KeyEvent.VK_T);
177         robot.keyRelease(KeyEvent.VK_T);
178         robot.keyRelease(KeyEvent.VK_META);
179         robot.waitForIdle();
180 
181         Object target = waitForAction(currentActionCount + 1);
182         boolean isDirectKey = windowIdentification instanceof Window && !selectColorPanel;
183         Object expectedTarget = getExpectedTarget(windowIdentification, isDirectKey);
184         if (!Objects.equals(target, expectedTarget)) {
185             failureCount++;
186             String configuration = getConfigurationName(windowIdentification, selectColorPanel);
187             System.err.println("***** Menu shortcut test failed for " + configuration + ". Expected: " + expectedTarget + ", Actual: " + target);
188         }
189     }
190 
performMenuItemTest(Object windowIdentification, boolean selectColorPanel)191     private void performMenuItemTest(Object windowIdentification, boolean selectColorPanel)
192     {
193         int currentActionCount = actionCounter;
194 
195         // Find the menu on the screen menu bar
196         // The location depends upon the application name which is the name of the first menu.
197         // Unfortunately, the application name can vary based on how the application is run.
198         // The work around is to make the menu and the menu item names very long.
199 
200         int menuBarX = 250;
201         int menuBarY = 11;
202         int menuItemX = menuBarX;
203         int menuItemY = 34;
204 
205         robot.mouseMove(menuBarX, menuBarY);
206         robot.delay(100);
207         robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
208         robot.delay(100);
209         robot.mouseMove(menuItemX, menuItemY);
210         robot.delay(100);
211         robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
212         robot.waitForIdle();
213 
214         Object target = waitForAction(currentActionCount + 1);
215         Object expectedTarget = getExpectedTarget(windowIdentification, false);
216         if (!Objects.equals(target, expectedTarget)) {
217             failureCount++;
218             String configuration = getConfigurationName(windowIdentification, selectColorPanel);
219             System.err.println("***** Menu item test failed for " + configuration + ". Expected: " + expectedTarget + ", Actual: " + target);
220         }
221     }
222 
getConfigurationName(Object windowIdentification, boolean selectColorPanel)223     private String getConfigurationName(Object windowIdentification, boolean selectColorPanel)
224     {
225         String name = "Unknown";
226         if (windowIdentification instanceof Window) {
227             Window w = (Window) windowIdentification;
228             name = getWindowTitle(w);
229         } else if (windowIdentification == NATIVE_WINDOW) {
230             name = "Native Window";
231         } else if (windowIdentification == COLOR_PANEL) {
232             name = "Color Panel";
233         }
234         if (selectColorPanel) {
235             return name + " with color panel";
236         } else {
237             return name;
238         }
239     }
240 
getExpectedTarget(Object windowIdentification, boolean isDirectKey)241     private Object getExpectedTarget(Object windowIdentification, boolean isDirectKey)
242     {
243         if (windowIdentification instanceof Window) {
244             Window w = (Window) windowIdentification;
245             String title = getWindowTitle(w);
246             if (isDirectKey) {
247                 title = title + " Key";
248             }
249             return title;
250         }
251         return "Application";
252     }
253 
getWindowTitle(Window w)254     private String getWindowTitle(Window w)
255     {
256         if (w instanceof Frame) {
257             Frame f = (Frame) w;
258             return f.getTitle();
259         }
260         if (w instanceof Dialog) {
261             Dialog d = (Dialog) w;
262             return d.getTitle();
263         }
264         throw new IllegalStateException();
265     }
266 
registerAction(Object target)267     private synchronized void registerAction(Object target)
268     {
269         actionCounter++;
270         actionTarget = target;
271     }
272 
waitForAction(int count)273     private synchronized Object waitForAction(int count)
274     {
275         try {
276             for (int i = 0; i < 10; i++) {
277                 if (actionCounter == count) {
278                     return actionTarget;
279                 }
280                 if (actionCounter > count) {
281                     throw new IllegalStateException();
282                 }
283                 wait(100);
284             }
285         } catch (InterruptedException ex) {
286         }
287         return "No Action";
288     }
289 
setupWindows(Object windowIdentification, boolean selectColorPanel)290     private void setupWindows(Object windowIdentification, boolean selectColorPanel)
291     {
292         clickOnWindowTitleBar(windowIdentification);
293         if (selectColorPanel) {
294             clickOnWindowTitleBar(COLOR_PANEL);
295         }
296     }
297 
clickOnWindowTitleBar(Object windowIdentification)298     private void clickOnWindowTitleBar(Object windowIdentification)
299     {
300         Rectangle bounds = getWindowBounds(windowIdentification);
301         int x = bounds.x + 70;  // to the right of the stoplight buttons
302         int y = bounds.y + 12;  // in the title bar
303         robot.mouseMove(x, y);
304         robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
305         robot.waitForIdle();
306         robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
307         robot.waitForIdle();
308     }
309 
getWindowBounds(Object windowIdentification)310     private Rectangle getWindowBounds(Object windowIdentification)
311     {
312         if (windowIdentification instanceof Window) {
313             Window w = (Window) windowIdentification;
314             return w.getBounds();
315         }
316         if (windowIdentification == COLOR_PANEL) {
317             return colorPanelBounds;
318         }
319         if (windowIdentification == NATIVE_WINDOW) {
320             return nativeWindowBounds;
321         }
322         throw new IllegalArgumentException();
323     }
324 
createMenuBar(String text, boolean isEnabled)325     JMenuBar createMenuBar(String text, boolean isEnabled)
326     {
327         JMenuBar mb = new JMenuBar();
328         // A very long name makes it more likely that the robot will hit the menu
329         JMenu menu = new JMenu("TestTestTestTestTestTestTestTestTestTest");
330         mb.add(menu);
331         JMenuItem item = new JMenuItem("TestTestTestTestTestTestTestTestTestTest");
332         item.setAccelerator(commandT);
333         item.setEnabled(isEnabled);
334         item.addActionListener(ev -> {
335             registerAction(text);
336         });
337         menu.add(item);
338         return mb;
339     }
340 
dispose()341     void dispose()
342     {
343         frame1.setVisible(false);
344         frame2.setVisible(false);
345         frame1.dispose();
346         frame2.dispose();
347         takedown();
348         Desktop.getDesktop().setDefaultMenuBar(null);
349         if (process != null) {
350             process.destroyForcibly();
351         }
352     }
353 
354     class MyAction
355         extends AbstractAction
356     {
357         String text;
358 
MyAction(String text)359         public MyAction(String text)
360         {
361             super("Test");
362 
363             this.text = text;
364         }
365 
366         @Override
actionPerformed(ActionEvent e)367         public void actionPerformed(ActionEvent e)
368         {
369             registerAction(text);
370         }
371     }
372 
setup()373     private static native void setup();
takedown()374     private static native void takedown();
activateApplication()375     private static native void activateApplication();
376 
main(String[] args)377     public static void main(String[] args) throws Exception
378     {
379         if (!System.getProperty("os.name").contains("OS X")) {
380             System.out.println("This test is for MacOS only. Automatically passed on other platforms.");
381             return;
382         }
383 
384         System.setProperty("apple.laf.useScreenMenuBar", "true");
385 
386         if (args.length != 0) {
387             Frame frame = new Frame();
388             MenuBar mb = new MenuBar();
389             mb.add(new Menu("Hello"));
390             frame.setMenuBar(mb);
391             frame.setBounds(400, 180, 300, 300);
392             frame.setVisible(true);
393             frame.toFront();
394             Thread.sleep(20_000);
395             System.exit(0);
396             return;
397         }
398 
399         try {
400             runSwing(() -> {
401                 theTest = new TestMainKeyWindow();
402             });
403             theTest.runTest();
404         } finally {
405             if (theTest != null) {
406                 runSwing(() -> {
407                     theTest.dispose();
408                 });
409             }
410         }
411     }
412 
runSwing(Runnable r)413     private static void runSwing(Runnable r)
414     {
415         try {
416             SwingUtilities.invokeAndWait(r);
417         } catch (InterruptedException e) {
418         } catch (InvocationTargetException e) {
419             throw new RuntimeException(e);
420         }
421     }
422 }
423