001    /*
002     * Created on Jan 30, 2008
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005     * the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011     * specific language governing permissions and limitations under the License.
012     *
013     * Copyright @2008-2010 the original author or authors.
014     */
015    package org.fest.swing.driver;
016    
017    import static java.lang.Boolean.getBoolean;
018    import static org.fest.swing.core.WindowAncestorFinder.windowAncestorOf;
019    import static org.fest.swing.driver.ComponentStateValidator.validateIsEnabledAndShowing;
020    import static org.fest.swing.driver.JMenuPopupMenuQuery.popupMenuOf;
021    import static org.fest.swing.driver.WindowMoveToFrontTask.toFront;
022    import static org.fest.swing.edt.GuiActionRunner.execute;
023    import static org.fest.swing.exception.ActionFailedException.actionFailure;
024    import static org.fest.swing.format.Formatting.format;
025    import static org.fest.swing.timing.Pause.pause;
026    import static org.fest.swing.util.Platform.isOSX;
027    import static org.fest.util.Strings.concat;
028    
029    import java.awt.Window;
030    
031    import javax.swing.*;
032    
033    import org.fest.swing.annotation.RunsInEDT;
034    import org.fest.swing.core.Robot;
035    import org.fest.swing.edt.GuiQuery;
036    import org.fest.swing.edt.GuiTask;
037    import org.fest.swing.exception.ActionFailedException;
038    
039    /**
040     * Understands functional testing of <code>{@link JMenuItem}</code>s:
041     * <ul>
042     * <li>user input simulation</li>
043     * <li>state verification</li>
044     * <li>property value query</li>
045     * </ul>
046     * This class is intended for internal use only. Please use the classes in the package
047     * <code>{@link org.fest.swing.fixture}</code> in your tests.
048     *
049     * @author Alex Ruiz
050     * @author Yvonne Wang
051     */
052    public class JMenuItemDriver extends JComponentDriver {
053    
054      /**
055       * Creates a new </code>{@link JMenuItemDriver}</code>.
056       * @param robot the robot to use to simulate user input.
057       */
058      public JMenuItemDriver(Robot robot) {
059        super(robot);
060      }
061    
062      /**
063       * Finds and selects the given <code>{@link JMenuItem}</code>.
064       * @param menuItem the <code>JMenuItem</code> to select.
065       * @throws IllegalStateException if the menu to select is disabled.
066       * @throws IllegalStateException if the menu to select is not showing on the screen.
067       * @throws ActionFailedException if the menu has a pop-up and it fails to show up.
068       */
069      @RunsInEDT
070      public void click(JMenuItem menuItem) {
071        show(menuItem);
072        doClick(menuItem);
073        ensurePopupIsShowing(menuItem);
074      }
075    
076      @RunsInEDT
077      private void show(JMenuItem menuItem) {
078        JMenuItemLocation location = locationOf(menuItem);
079        activateParentIfIsAMenu(location);
080        moveParentWindowToFront(location);
081        if (menuItem instanceof JMenu && !location.inMenuBar()) waitForSubMenuToShow();
082      }
083    
084      @RunsInEDT
085      private static JMenuItemLocation locationOf(final JMenuItem menuItem) {
086        return execute(new GuiQuery<JMenuItemLocation>() {
087          protected JMenuItemLocation executeInEDT() {
088            return new JMenuItemLocation(menuItem);
089          }
090    
091        });
092      }
093    
094      @RunsInEDT
095      private void activateParentIfIsAMenu(JMenuItemLocation location) {
096        if (!location.isParentAMenu()) return;
097        click((JMenuItem)location.parentOrInvoker());
098      }
099    
100      @RunsInEDT
101      private void moveParentWindowToFront(JMenuItemLocation location) {
102        if (!location.inMenuBar()) return;
103        // TODO windowAncestorOf is not being called in EDT
104        moveToFront(windowAncestorOf(location.parentOrInvoker()));
105      }
106    
107      @RunsInEDT
108      private void doClick(JMenuItem menuItem) {
109        if (isMacOSMenuBar()) {
110          validateAndDoClick(menuItem);
111          return;
112        }
113        super.click(menuItem);
114        robot.waitForIdle();
115      }
116    
117      private boolean isMacOSMenuBar() {
118        return isOSX() && (getBoolean("apple.laf.useScreenMenuBar") || getBoolean("com.apple.macos.useScreenMenuBar"));
119      }
120    
121      @RunsInEDT
122      private static void validateAndDoClick(final JMenuItem menuItem) {
123        execute(new GuiTask() {
124          protected void executeInEDT() {
125            validateIsEnabledAndShowing(menuItem);
126            menuItem.doClick();
127          }
128        });
129      }
130    
131      @RunsInEDT
132      private void ensurePopupIsShowing(JMenuItem menuItem) {
133        if (!(menuItem instanceof JMenu)) return;
134        JPopupMenu popup = popupMenuOf((JMenu)menuItem);
135        // TODO review EDT access
136        if (!waitForShowing(popup, robot.settings().timeoutToFindPopup()))
137          throw actionFailure(concat("Clicking on menu item <", format(menuItem), "> never showed a pop-up menu"));
138        waitForSubMenuToShow();
139      }
140    
141      private void waitForSubMenuToShow() {
142        pause(robot.settings().timeoutToFindSubMenu());
143      }
144    
145      @RunsInEDT
146      private void moveToFront(Window w) {
147        if (w == null) return;
148        // Make sure the window is in front, or its menus may be obscured by another window.
149        toFront(w);
150        robot.waitForIdle();
151        robot.moveMouse(w);
152      }
153    }