/* * Copyright (c) 2018, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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. */ /** * @test * @key headful * @bug 8194327 * @summary [macosx] AWT windows have incorrect main/key window behaviors * @author Alan Snyder * @run main/othervm/native Test * @requires (os.family == "mac") */ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.Objects; import javax.swing.*; public class Test { static Test theTest; KeyStroke commandT = KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.META_DOWN_MASK); int nextX = 100; private final MyFrame frame1; private final MyFrame frame2; private final Object COLOR_PANEL = "Color Panel"; private final Object NATIVE_WINDOW = "Native Window"; // these bounds must agree with the native code that creates the windows private Rectangle colorPanelBounds = new Rectangle(100, 400, 225, 400); // approximate is OK private Rectangle nativeWindowBounds = new Rectangle(100, 200, 150, 150); private Robot robot; private int actionCounter; private Object actionTarget; private int failureCount; public Test() { System.loadLibrary("test8194327"); JMenuBar defaultMenuBar = createMenuBar("Application", true); Desktop.getDesktop().setDefaultMenuBar(defaultMenuBar); setup(); frame1 = new MyFrame("Frame 1"); frame2 = new MyFrame("Frame 2"); frame1.setVisible(true); frame2.setVisible(true); try { robot = new Robot(); robot.setAutoDelay(50); } catch (AWTException ex) { throw new RuntimeException(ex); } } class MyFrame extends JFrame { public MyFrame(String title) throws HeadlessException { super(title); JMenuBar mainMenuBar = createMenuBar(title, true); setJMenuBar(mainMenuBar); setBounds(nextX, 50, 200, 130); nextX += 250; JComponent contentPane = new JPanel(); setContentPane(contentPane); contentPane.setLayout(new FlowLayout()); contentPane.add(new JCheckBox("foo", true)); InputMap inputMap = contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); inputMap.put(commandT, "test"); ActionMap actionMap = contentPane.getActionMap(); actionMap.put("test", new MyAction(title + " Key")); } } private void runTest() { failureCount = 0; robot.waitForIdle(); performTest(frame1, false); performTest(frame1, true); performTest(frame2, false); performTest(frame2, true); performTest(NATIVE_WINDOW, false); performTest(NATIVE_WINDOW, true); performTest(COLOR_PANEL, false); if (failureCount > 0) { throw new RuntimeException("Test failed: " + failureCount + " failure(s)"); } } private void performTest(Object windowIdentification, boolean selectColorPanel) { setupWindows(windowIdentification, selectColorPanel); performMenuShortcutTest(windowIdentification, selectColorPanel); performMenuItemTest(windowIdentification, selectColorPanel); // test deactivating and reactivating the application // the window state and behavior should be restored try { Runtime.getRuntime().exec("/usr/bin/open /"); } catch (IOException ex) { throw new RuntimeException("Unable to deactivate test application"); } robot.delay(1000); activateApplication(); robot.delay(1000); performMenuShortcutTest(windowIdentification, selectColorPanel); performMenuItemTest(windowIdentification, selectColorPanel); } private void performMenuShortcutTest(Object windowIdentification, boolean selectColorPanel) { int currentActionCount = actionCounter; // Perform the menu shortcut robot.keyPress(KeyEvent.VK_META); robot.keyPress(KeyEvent.VK_T); robot.keyRelease(KeyEvent.VK_T); robot.keyRelease(KeyEvent.VK_META); robot.waitForIdle(); Object target = waitForAction(currentActionCount + 1); boolean isDirectKey = windowIdentification instanceof Window && !selectColorPanel; Object expectedTarget = getExpectedTarget(windowIdentification, isDirectKey); if (!Objects.equals(target, expectedTarget)) { failureCount++; String configuration = getConfigurationName(windowIdentification, selectColorPanel); System.err.println("***** Menu shortcut test failed for " + configuration + ". Expected: " + expectedTarget + ", Actual: " + target); } } private void performMenuItemTest(Object windowIdentification, boolean selectColorPanel) { int currentActionCount = actionCounter; // Find the menu on the screen menu bar // The location depends upon the application name which is the name of the first menu. // Unfortunately, the application name can vary based on how the application is run. // The work around is to make the menu and the menu item names very long. int menuBarX = 250; int menuBarY = 11; int menuItemX = menuBarX; int menuItemY = 34; robot.mouseMove(menuBarX, menuBarY); robot.delay(100); robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); robot.delay(100); robot.mouseMove(menuItemX, menuItemY); robot.delay(100); robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); robot.waitForIdle(); Object target = waitForAction(currentActionCount + 1); Object expectedTarget = getExpectedTarget(windowIdentification, false); if (!Objects.equals(target, expectedTarget)) { failureCount++; String configuration = getConfigurationName(windowIdentification, selectColorPanel); System.err.println("***** Menu item test failed for " + configuration + ". Expected: " + expectedTarget + ", Actual: " + target); } } private String getConfigurationName(Object windowIdentification, boolean selectColorPanel) { String name = "Unknown"; if (windowIdentification instanceof Window) { Window w = (Window) windowIdentification; name = getWindowTitle(w); } else if (windowIdentification == NATIVE_WINDOW) { name = "Native Window"; } else if (windowIdentification == COLOR_PANEL) { name = "Color Panel"; } if (selectColorPanel) { return name + " with color panel"; } else { return name; } } private Object getExpectedTarget(Object windowIdentification, boolean isDirectKey) { if (windowIdentification instanceof Window) { Window w = (Window) windowIdentification; String title = getWindowTitle(w); if (isDirectKey) { title = title + " Key"; } return title; } return "Application"; } private String getWindowTitle(Window w) { if (w instanceof Frame) { Frame f = (Frame) w; return f.getTitle(); } if (w instanceof Dialog) { Dialog d = (Dialog) w; return d.getTitle(); } throw new IllegalStateException(); } private synchronized void registerAction(Object target) { actionCounter++; actionTarget = target; } private synchronized Object waitForAction(int count) { try { for (int i = 0; i < 10; i++) { if (actionCounter == count) { return actionTarget; } if (actionCounter > count) { throw new IllegalStateException(); } wait(100); } } catch (InterruptedException ex) { } return "No Action"; } private void setupWindows(Object windowIdentification, boolean selectColorPanel) { clickOnWindowTitleBar(windowIdentification); if (selectColorPanel) { clickOnWindowTitleBar(COLOR_PANEL); } } private void clickOnWindowTitleBar(Object windowIdentification) { Rectangle bounds = getWindowBounds(windowIdentification); int x = bounds.x + 70; // to the right of the stoplight buttons int y = bounds.y + 12; // in the title bar robot.mouseMove(x, y); robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); robot.waitForIdle(); robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); robot.waitForIdle(); } private Rectangle getWindowBounds(Object windowIdentification) { if (windowIdentification instanceof Window) { Window w = (Window) windowIdentification; return w.getBounds(); } if (windowIdentification == COLOR_PANEL) { return colorPanelBounds; } if (windowIdentification == NATIVE_WINDOW) { return nativeWindowBounds; } throw new IllegalArgumentException(); } JMenuBar createMenuBar(String text, boolean isEnabled) { JMenuBar mb = new JMenuBar(); // A very long name makes it more likely that the robot will hit the menu JMenu menu = new JMenu("TestTestTestTestTestTestTestTestTestTest"); mb.add(menu); JMenuItem item = new JMenuItem("TestTestTestTestTestTestTestTestTestTest"); item.setAccelerator(commandT); item.setEnabled(isEnabled); item.addActionListener(ev -> { registerAction(text); }); menu.add(item); return mb; } void dispose() { frame1.setVisible(false); frame2.setVisible(false); frame1.dispose(); frame2.dispose(); takedown(); Desktop.getDesktop().setDefaultMenuBar(null); } class MyAction extends AbstractAction { String text; public MyAction(String text) { super("Test"); this.text = text; } @Override public void actionPerformed(ActionEvent e) { registerAction(text); } } private static native void setup(); private static native void takedown(); private static native void activateApplication(); public static void main(String[] args) { if (!System.getProperty("os.name").contains("OS X")) { System.out.println("This test is for MacOS only. Automatically passed on other platforms."); return; } System.setProperty("apple.laf.useScreenMenuBar", "true"); try { runSwing(() -> { theTest = new Test(); }); theTest.runTest(); } finally { if (theTest != null) { runSwing(() -> { theTest.dispose(); }); } } } private static void runSwing(Runnable r) { try { SwingUtilities.invokeAndWait(r); } catch (InterruptedException e) { } catch (InvocationTargetException e) { throw new RuntimeException(e); } } }