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 }