1 /* 2 * Copyright (c) 2018, 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 * @run main/othervm/native TestMainKeyWindow 33 * @requires (os.family == "mac") 34 */ 35 36 import java.awt.*; 37 import java.awt.event.ActionEvent; 38 import java.awt.event.InputEvent; 39 import java.awt.event.KeyEvent; 40 import java.io.IOException; 41 import java.lang.reflect.InvocationTargetException; 42 import java.util.Objects; 43 import javax.swing.*; 44 45 public class TestMainKeyWindow 46 { 47 static TestMainKeyWindow theTest; 48 49 KeyStroke commandT = KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.META_DOWN_MASK); 50 51 int nextX = 100; 52 53 private final MyFrame frame1; 54 private final MyFrame frame2; 55 private final Object COLOR_PANEL = "Color Panel"; 56 private final Object NATIVE_WINDOW = "Native Window"; 57 58 // these bounds must agree with the native code that creates the windows 59 private Rectangle colorPanelBounds = new Rectangle(100, 400, 225, 400); // approximate is OK 60 private Rectangle nativeWindowBounds = new Rectangle(100, 200, 150, 150); 61 62 private Robot robot; 63 64 private int actionCounter; 65 private Object actionTarget; 66 67 private int failureCount; 68 private boolean isFinderWindowOpened; 69 70 public TestMainKeyWindow() 71 { 72 System.loadLibrary("testMainKeyWindow"); 73 74 JMenuBar defaultMenuBar = createMenuBar("Application", true); 75 Desktop.getDesktop().setDefaultMenuBar(defaultMenuBar); 76 77 setup(); 78 79 frame1 = new MyFrame("Frame 1"); 80 frame2 = new MyFrame("Frame 2"); 81 frame1.setVisible(true); 82 frame2.setVisible(true); 83 84 try { 85 robot = new Robot(); 86 robot.setAutoDelay(50); 87 } catch (AWTException ex) { 88 throw new RuntimeException(ex); 89 } 90 } 91 92 class MyFrame 93 extends JFrame 94 { 95 public MyFrame(String title) 96 throws HeadlessException 97 { 98 super(title); 99 100 JMenuBar mainMenuBar = createMenuBar(title, true); 101 setJMenuBar(mainMenuBar); 102 setBounds(nextX, 50, 200, 130); 103 nextX += 250; 104 JComponent contentPane = new JPanel(); 105 setContentPane(contentPane); 106 contentPane.setLayout(new FlowLayout()); 107 contentPane.add(new JCheckBox("foo", true)); 108 InputMap inputMap = contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 109 inputMap.put(commandT, "test"); 110 ActionMap actionMap = contentPane.getActionMap(); 111 actionMap.put("test", new MyAction(title + " Key")); 112 } 113 } 114 115 private void runTest() 116 { 117 failureCount = 0; 118 robot.waitForIdle(); 119 performTest(frame1, false); 120 performTest(frame1, true); 121 performTest(frame2, false); 122 performTest(frame2, true); 123 performTest(NATIVE_WINDOW, false); 124 performTest(NATIVE_WINDOW, true); 125 performTest(COLOR_PANEL, false); 126 if (failureCount > 0) { 127 throw new RuntimeException("Test failed: " + failureCount + " failure(s)"); 128 } 129 } 130 131 private void performTest(Object windowIdentification, boolean selectColorPanel) 132 { 133 setupWindows(windowIdentification, selectColorPanel); 134 135 performMenuShortcutTest(windowIdentification, selectColorPanel); 136 performMenuItemTest(windowIdentification, selectColorPanel); 137 138 // test deactivating and reactivating the application 139 // the window state and behavior should be restored 140 141 try { 142 Runtime.getRuntime().exec("/usr/bin/open /"); 143 isFinderWindowOpened = true; 144 } catch (IOException ex) { 145 throw new RuntimeException("Unable to deactivate test application"); 146 } 147 robot.delay(1000); 148 activateApplication(); 149 robot.delay(1000); 150 151 performMenuShortcutTest(windowIdentification, selectColorPanel); 152 performMenuItemTest(windowIdentification, selectColorPanel); 153 } 154 155 private void performMenuShortcutTest(Object windowIdentification, boolean selectColorPanel) 156 { 157 int currentActionCount = actionCounter; 158 159 // Perform the menu shortcut 160 robot.keyPress(KeyEvent.VK_META); 161 robot.keyPress(KeyEvent.VK_T); 162 robot.keyRelease(KeyEvent.VK_T); 163 robot.keyRelease(KeyEvent.VK_META); 164 robot.waitForIdle(); 165 166 Object target = waitForAction(currentActionCount + 1); 167 boolean isDirectKey = windowIdentification instanceof Window && !selectColorPanel; 168 Object expectedTarget = getExpectedTarget(windowIdentification, isDirectKey); 169 if (!Objects.equals(target, expectedTarget)) { 170 failureCount++; 171 String configuration = getConfigurationName(windowIdentification, selectColorPanel); 172 System.err.println("***** Menu shortcut test failed for " + configuration + ". Expected: " + expectedTarget + ", Actual: " + target); 173 } 174 } 175 176 private void performMenuItemTest(Object windowIdentification, boolean selectColorPanel) 177 { 178 int currentActionCount = actionCounter; 179 180 // Find the menu on the screen menu bar 181 // The location depends upon the application name which is the name of the first menu. 182 // Unfortunately, the application name can vary based on how the application is run. 183 // The work around is to make the menu and the menu item names very long. 184 185 int menuBarX = 250; 186 int menuBarY = 11; 187 int menuItemX = menuBarX; 188 int menuItemY = 34; 189 190 robot.mouseMove(menuBarX, menuBarY); 191 robot.delay(100); 192 robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); 193 robot.delay(100); 194 robot.mouseMove(menuItemX, menuItemY); 195 robot.delay(100); 196 robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); 197 robot.waitForIdle(); 198 199 Object target = waitForAction(currentActionCount + 1); 200 Object expectedTarget = getExpectedTarget(windowIdentification, false); 201 if (!Objects.equals(target, expectedTarget)) { 202 failureCount++; 203 String configuration = getConfigurationName(windowIdentification, selectColorPanel); 204 System.err.println("***** Menu item test failed for " + configuration + ". Expected: " + expectedTarget + ", Actual: " + target); 205 } 206 } 207 208 private String getConfigurationName(Object windowIdentification, boolean selectColorPanel) 209 { 210 String name = "Unknown"; 211 if (windowIdentification instanceof Window) { 212 Window w = (Window) windowIdentification; 213 name = getWindowTitle(w); 214 } else if (windowIdentification == NATIVE_WINDOW) { 215 name = "Native Window"; 216 } else if (windowIdentification == COLOR_PANEL) { 217 name = "Color Panel"; 218 } 219 if (selectColorPanel) { 220 return name + " with color panel"; 221 } else { 222 return name; 223 } 224 } 225 226 private Object getExpectedTarget(Object windowIdentification, boolean isDirectKey) 227 { 228 if (windowIdentification instanceof Window) { 229 Window w = (Window) windowIdentification; 230 String title = getWindowTitle(w); 231 if (isDirectKey) { 232 title = title + " Key"; 233 } 234 return title; 235 } 236 return "Application"; 237 } 238 239 private String getWindowTitle(Window w) 240 { 241 if (w instanceof Frame) { 242 Frame f = (Frame) w; 243 return f.getTitle(); 244 } 245 if (w instanceof Dialog) { 246 Dialog d = (Dialog) w; 247 return d.getTitle(); 248 } 249 throw new IllegalStateException(); 250 } 251 252 private synchronized void registerAction(Object target) 253 { 254 actionCounter++; 255 actionTarget = target; 256 } 257 258 private synchronized Object waitForAction(int count) 259 { 260 try { 261 for (int i = 0; i < 10; i++) { 262 if (actionCounter == count) { 263 return actionTarget; 264 } 265 if (actionCounter > count) { 266 throw new IllegalStateException(); 267 } 268 wait(100); 269 } 270 } catch (InterruptedException ex) { 271 } 272 return "No Action"; 273 } 274 275 private void setupWindows(Object windowIdentification, boolean selectColorPanel) 276 { 277 clickOnWindowTitleBar(windowIdentification); 278 if (selectColorPanel) { 279 clickOnWindowTitleBar(COLOR_PANEL); 280 } 281 } 282 283 private void clickOnWindowTitleBar(Object windowIdentification) 284 { 285 Rectangle bounds = getWindowBounds(windowIdentification); 286 int x = bounds.x + 70; // to the right of the stoplight buttons 287 int y = bounds.y + 12; // in the title bar 288 robot.mouseMove(x, y); 289 robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); 290 robot.waitForIdle(); 291 robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); 292 robot.waitForIdle(); 293 } 294 295 private Rectangle getWindowBounds(Object windowIdentification) 296 { 297 if (windowIdentification instanceof Window) { 298 Window w = (Window) windowIdentification; 299 return w.getBounds(); 300 } 301 if (windowIdentification == COLOR_PANEL) { 302 return colorPanelBounds; 303 } 304 if (windowIdentification == NATIVE_WINDOW) { 305 return nativeWindowBounds; 306 } 307 throw new IllegalArgumentException(); 308 } 309 310 JMenuBar createMenuBar(String text, boolean isEnabled) 311 { 312 JMenuBar mb = new JMenuBar(); 313 // A very long name makes it more likely that the robot will hit the menu 314 JMenu menu = new JMenu("TestTestTestTestTestTestTestTestTestTest"); 315 mb.add(menu); 316 JMenuItem item = new JMenuItem("TestTestTestTestTestTestTestTestTestTest"); 317 item.setAccelerator(commandT); 318 item.setEnabled(isEnabled); 319 item.addActionListener(ev -> { 320 registerAction(text); 321 }); 322 menu.add(item); 323 return mb; 324 } 325 326 void dispose() 327 { 328 frame1.setVisible(false); 329 frame2.setVisible(false); 330 frame1.dispose(); 331 frame2.dispose(); 332 takedown(); 333 Desktop.getDesktop().setDefaultMenuBar(null); 334 if (isFinderWindowOpened) { 335 try { 336 String[] cmd = { "/usr/bin/osascript", "-e", "tell application \"Finder\" to close window 1" }; 337 Runtime.getRuntime().exec(cmd); 338 } catch (IOException ex) { 339 } 340 } 341 } 342 343 class MyAction 344 extends AbstractAction 345 { 346 String text; 347 348 public MyAction(String text) 349 { 350 super("Test"); 351 352 this.text = text; 353 } 354 355 @Override 356 public void actionPerformed(ActionEvent e) 357 { 358 registerAction(text); 359 } 360 } 361 362 private static native void setup(); 363 private static native void takedown(); 364 private static native void activateApplication(); 365 366 public static void main(String[] args) 367 { 368 if (!System.getProperty("os.name").contains("OS X")) { 369 System.out.println("This test is for MacOS only. Automatically passed on other platforms."); 370 return; 371 } 372 373 System.setProperty("apple.laf.useScreenMenuBar", "true"); 374 375 try { 376 runSwing(() -> { 377 theTest = new TestMainKeyWindow(); 378 }); 379 theTest.runTest(); 380 } finally { 381 if (theTest != null) { 382 runSwing(() -> { 383 theTest.dispose(); 384 }); 385 } 386 } 387 } 388 389 private static void runSwing(Runnable r) 390 { 391 try { 392 SwingUtilities.invokeAndWait(r); 393 } catch (InterruptedException e) { 394 } catch (InvocationTargetException e) { 395 throw new RuntimeException(e); 396 } 397 } 398 }