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 = 130; 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(130, 300, 225, 400); // approximate is OK 60 private Rectangle nativeWindowBounds = new Rectangle(130, 200, 200, 100); 61 62 private Robot robot; 63 64 private int actionCounter; 65 private Object actionTarget; 66 67 private int failureCount; 68 private boolean isApplicationOpened; 69 TestMainKeyWindow()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 { MyFrame(String title)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, 60, 200, 90); 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 runTest()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 performTest(Object windowIdentification, boolean selectColorPanel)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 openOtherApplication(); 142 activateApplication(); 143 robot.delay(1000); 144 145 performMenuShortcutTest(windowIdentification, selectColorPanel); 146 performMenuItemTest(windowIdentification, selectColorPanel); 147 } 148 openOtherApplication()149 private void openOtherApplication() { 150 try { 151 String[] cmd = { "/usr/bin/open", "/Applications/System Preferences.app" }; 152 Runtime.getRuntime().exec(cmd); 153 if (!isApplicationOpened) { 154 String[] cmd2 = { "/usr/bin/osascript", "-e", 155 "tell application \"System Preferences\" to set bounds of window 1 to {400, 180, 1068, 821}" }; 156 Runtime.getRuntime().exec(cmd2); 157 } 158 isApplicationOpened = true; 159 } catch (IOException ex) { 160 throw new RuntimeException("Unable to deactivate test application"); 161 } 162 robot.delay(1000); 163 } 164 performMenuShortcutTest(Object windowIdentification, boolean selectColorPanel)165 private void performMenuShortcutTest(Object windowIdentification, boolean selectColorPanel) 166 { 167 int currentActionCount = actionCounter; 168 169 // Perform the menu shortcut 170 robot.keyPress(KeyEvent.VK_META); 171 robot.keyPress(KeyEvent.VK_T); 172 robot.keyRelease(KeyEvent.VK_T); 173 robot.keyRelease(KeyEvent.VK_META); 174 robot.waitForIdle(); 175 176 Object target = waitForAction(currentActionCount + 1); 177 boolean isDirectKey = windowIdentification instanceof Window && !selectColorPanel; 178 Object expectedTarget = getExpectedTarget(windowIdentification, isDirectKey); 179 if (!Objects.equals(target, expectedTarget)) { 180 failureCount++; 181 String configuration = getConfigurationName(windowIdentification, selectColorPanel); 182 System.err.println("***** Menu shortcut test failed for " + configuration + ". Expected: " + expectedTarget + ", Actual: " + target); 183 } 184 } 185 performMenuItemTest(Object windowIdentification, boolean selectColorPanel)186 private void performMenuItemTest(Object windowIdentification, boolean selectColorPanel) 187 { 188 int currentActionCount = actionCounter; 189 190 // Find the menu on the screen menu bar 191 // The location depends upon the application name which is the name of the first menu. 192 // Unfortunately, the application name can vary based on how the application is run. 193 // The work around is to make the menu and the menu item names very long. 194 195 int menuBarX = 250; 196 int menuBarY = 11; 197 int menuItemX = menuBarX; 198 int menuItemY = 34; 199 200 robot.mouseMove(menuBarX, menuBarY); 201 robot.delay(100); 202 robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); 203 robot.delay(100); 204 robot.mouseMove(menuItemX, menuItemY); 205 robot.delay(100); 206 robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); 207 robot.waitForIdle(); 208 209 Object target = waitForAction(currentActionCount + 1); 210 Object expectedTarget = getExpectedTarget(windowIdentification, false); 211 if (!Objects.equals(target, expectedTarget)) { 212 failureCount++; 213 String configuration = getConfigurationName(windowIdentification, selectColorPanel); 214 System.err.println("***** Menu item test failed for " + configuration + ". Expected: " + expectedTarget + ", Actual: " + target); 215 } 216 } 217 getConfigurationName(Object windowIdentification, boolean selectColorPanel)218 private String getConfigurationName(Object windowIdentification, boolean selectColorPanel) 219 { 220 String name = "Unknown"; 221 if (windowIdentification instanceof Window) { 222 Window w = (Window) windowIdentification; 223 name = getWindowTitle(w); 224 } else if (windowIdentification == NATIVE_WINDOW) { 225 name = "Native Window"; 226 } else if (windowIdentification == COLOR_PANEL) { 227 name = "Color Panel"; 228 } 229 if (selectColorPanel) { 230 return name + " with color panel"; 231 } else { 232 return name; 233 } 234 } 235 getExpectedTarget(Object windowIdentification, boolean isDirectKey)236 private Object getExpectedTarget(Object windowIdentification, boolean isDirectKey) 237 { 238 if (windowIdentification instanceof Window) { 239 Window w = (Window) windowIdentification; 240 String title = getWindowTitle(w); 241 if (isDirectKey) { 242 title = title + " Key"; 243 } 244 return title; 245 } 246 return "Application"; 247 } 248 getWindowTitle(Window w)249 private String getWindowTitle(Window w) 250 { 251 if (w instanceof Frame) { 252 Frame f = (Frame) w; 253 return f.getTitle(); 254 } 255 if (w instanceof Dialog) { 256 Dialog d = (Dialog) w; 257 return d.getTitle(); 258 } 259 throw new IllegalStateException(); 260 } 261 registerAction(Object target)262 private synchronized void registerAction(Object target) 263 { 264 actionCounter++; 265 actionTarget = target; 266 } 267 waitForAction(int count)268 private synchronized Object waitForAction(int count) 269 { 270 try { 271 for (int i = 0; i < 10; i++) { 272 if (actionCounter == count) { 273 return actionTarget; 274 } 275 if (actionCounter > count) { 276 throw new IllegalStateException(); 277 } 278 wait(100); 279 } 280 } catch (InterruptedException ex) { 281 } 282 return "No Action"; 283 } 284 setupWindows(Object windowIdentification, boolean selectColorPanel)285 private void setupWindows(Object windowIdentification, boolean selectColorPanel) 286 { 287 clickOnWindowTitleBar(windowIdentification); 288 if (selectColorPanel) { 289 clickOnWindowTitleBar(COLOR_PANEL); 290 } 291 } 292 clickOnWindowTitleBar(Object windowIdentification)293 private void clickOnWindowTitleBar(Object windowIdentification) 294 { 295 Rectangle bounds = getWindowBounds(windowIdentification); 296 int x = bounds.x + 70; // to the right of the stoplight buttons 297 int y = bounds.y + 12; // in the title bar 298 robot.mouseMove(x, y); 299 robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); 300 robot.waitForIdle(); 301 robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); 302 robot.waitForIdle(); 303 } 304 getWindowBounds(Object windowIdentification)305 private Rectangle getWindowBounds(Object windowIdentification) 306 { 307 if (windowIdentification instanceof Window) { 308 Window w = (Window) windowIdentification; 309 return w.getBounds(); 310 } 311 if (windowIdentification == COLOR_PANEL) { 312 return colorPanelBounds; 313 } 314 if (windowIdentification == NATIVE_WINDOW) { 315 return nativeWindowBounds; 316 } 317 throw new IllegalArgumentException(); 318 } 319 createMenuBar(String text, boolean isEnabled)320 JMenuBar createMenuBar(String text, boolean isEnabled) 321 { 322 JMenuBar mb = new JMenuBar(); 323 // A very long name makes it more likely that the robot will hit the menu 324 JMenu menu = new JMenu("TestTestTestTestTestTestTestTestTestTest"); 325 mb.add(menu); 326 JMenuItem item = new JMenuItem("TestTestTestTestTestTestTestTestTestTest"); 327 item.setAccelerator(commandT); 328 item.setEnabled(isEnabled); 329 item.addActionListener(ev -> { 330 registerAction(text); 331 }); 332 menu.add(item); 333 return mb; 334 } 335 dispose()336 void dispose() 337 { 338 frame1.setVisible(false); 339 frame2.setVisible(false); 340 frame1.dispose(); 341 frame2.dispose(); 342 takedown(); 343 Desktop.getDesktop().setDefaultMenuBar(null); 344 if (isApplicationOpened) { 345 try { 346 String[] cmd = { "/usr/bin/osascript", "-e", "tell application \"System Preferences\" to close window 1" }; 347 Process p = Runtime.getRuntime().exec(cmd); 348 p.waitFor(); 349 } catch (IOException | InterruptedException ex) { 350 } 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) 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 try { 387 runSwing(() -> { 388 theTest = new TestMainKeyWindow(); 389 }); 390 theTest.runTest(); 391 } finally { 392 if (theTest != null) { 393 runSwing(() -> { 394 theTest.dispose(); 395 }); 396 } 397 } 398 } 399 runSwing(Runnable r)400 private static void runSwing(Runnable r) 401 { 402 try { 403 SwingUtilities.invokeAndWait(r); 404 } catch (InterruptedException e) { 405 } catch (InvocationTargetException e) { 406 throw new RuntimeException(e); 407 } 408 } 409 } 410