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