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 }