/******************************************************************************* * Copyright (c) 2012 BREDEX GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * BREDEX GmbH - initial API and implementation *******************************************************************************/ package org.eclipse.jubula.rc.swt.tester.adapter; import org.eclipse.jubula.rc.common.driver.ClickOptions; import org.eclipse.jubula.rc.common.driver.IEventMatcher; import org.eclipse.jubula.rc.common.driver.IEventThreadQueuer; import org.eclipse.jubula.rc.common.driver.IRobot; import org.eclipse.jubula.rc.common.driver.IRobotEventConfirmer; import org.eclipse.jubula.rc.common.driver.IRobotEventInterceptor; import org.eclipse.jubula.rc.common.driver.IRunnable; import org.eclipse.jubula.rc.common.driver.InterceptorOptions; import org.eclipse.jubula.rc.common.exception.RobotException; import org.eclipse.jubula.rc.common.exception.StepExecutionException; import org.eclipse.jubula.rc.common.listener.EventLock; import org.eclipse.jubula.rc.common.logger.AutServerLogger; import org.eclipse.jubula.rc.common.tester.adapter.interfaces.IMenuComponent; import org.eclipse.jubula.rc.common.tester.adapter.interfaces.IMenuItemComponent; import org.eclipse.jubula.rc.swt.driver.EventThreadQueuerSwtImpl; import org.eclipse.jubula.rc.swt.driver.RobotFactorySwtImpl; import org.eclipse.jubula.rc.swt.driver.SelectionSwtEventMatcher; import org.eclipse.jubula.rc.swt.driver.ShowSwtEventMatcher; import org.eclipse.jubula.rc.swt.tester.util.CAPUtil; import org.eclipse.jubula.rc.swt.tester.util.EventListener; import org.eclipse.jubula.rc.swt.tester.util.EventListener.Condition; import org.eclipse.jubula.rc.swt.utils.SwtUtils; import org.eclipse.jubula.tools.internal.constants.TimeoutConstants; import org.eclipse.jubula.tools.internal.i18n.I18n; import org.eclipse.jubula.tools.internal.objects.event.EventFactory; import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent; import org.eclipse.jubula.tools.internal.utils.EnvironmentUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; /** * Implements the MenuItem interface for adapting a <code>SWT.MenuItem</code> * * @author BREDEX GmbH */ public class MenuItemAdapter extends AbstractComponentAdapter implements IMenuItemComponent { /** The logging. */ private static AutServerLogger log = new AutServerLogger(MenuItemAdapter.class); /** the MenuItem from the AUT*/ private MenuItem m_menuItem; /** * * @param component graphics component which will be adapted */ public MenuItemAdapter(Object component) { super(); m_menuItem = (MenuItem) component; } /** * Gets the IEventThreadQueuer. * * @return The Robot * @throws RobotException * If the Robot cannot be created. */ protected IRobot getRobot() throws RobotException { return getRobotFactory().getRobot(); } /** * {@inheritDoc} */ public Object getRealComponent() { return m_menuItem; } /** * {@inheritDoc} */ public void setComponent(Object element) { m_menuItem = (MenuItem) element; } /** * {@inheritDoc} */ public String getText() { return getEventThreadQueuer().invokeAndWait( "getText", new IRunnable<String>() { //$NON-NLS-1$ public String run() { return CAPUtil.getWidgetText(m_menuItem, SwtUtils.removeMnemonics(m_menuItem.getText())); } }); } /** * {@inheritDoc} */ public boolean isEnabled() { return getEventThreadQueuer().invokeAndWait( "isEnabled", new IRunnable<Boolean>() { //$NON-NLS-1$ public Boolean run() throws StepExecutionException { return m_menuItem.isEnabled(); } }); } /** * {@inheritDoc} */ public boolean isExisting() { if (m_menuItem != null) { return true; } return false; } /** * {@inheritDoc} */ public boolean isSelected() { return getEventThreadQueuer().invokeAndWait( "isSelected", new IRunnable<Boolean>() { //$NON-NLS-1$ public Boolean run() throws StepExecutionException { return m_menuItem.getSelection(); } }); } /** * {@inheritDoc} * */ public boolean isShowing() { if (m_menuItem == null) { // There is no check for showing return false; } return true; } /** * {@inheritDoc} */ public IMenuComponent getMenu() { Menu menu = getEventThreadQueuer().invokeAndWait( "getItems", new IRunnable<Menu>() { //$NON-NLS-1$ public Menu run() { return m_menuItem.getMenu(); } }); return new MenuAdapter(menu); } /** * {@inheritDoc} */ public boolean hasSubMenu() { if (getMenu() != null) { return true; } return false; } /** * Checks whether the given menu item is a separator. * This method runs in the GUI thread. * @return <code>true</code> if <code>menuItem</code> is a separator item. * Otherwise <code>false</code>. */ public boolean isSeparator() { return getEventThreadQueuer().invokeAndWait( ".isSeparator", new IRunnable<Boolean>() { //$NON-NLS-1$ public Boolean run() throws StepExecutionException { return (m_menuItem.getStyle() & SWT.SEPARATOR) != 0; } }); } /** * {@inheritDoc} */ public void selectMenuItem() { Rectangle bounds = getMenuItemBounds(); Rectangle nullBounds = new Rectangle(0, 0, 0, 0); if (bounds.equals(nullBounds)) { selectProgramatically(); } else { clickMenuItem(getRobot(), m_menuItem, 1); } } /** * {@inheritDoc} */ public IMenuComponent openSubMenu() { final MenuItem menuItem = m_menuItem; MenuShownCondition cond = new MenuShownCondition(menuItem); EventLock lock = new EventLock(); final EventListener listener = new EventListener(lock, cond); final Display d = menuItem.getDisplay(); final IEventThreadQueuer queuer = new EventThreadQueuerSwtImpl(); queuer.invokeAndWait("addMenuShownListeners", new IRunnable<Void>() { //$NON-NLS-1$ public Void run() { d.addFilter(SWT.Show, listener); return null; } }); try { // Menu bar items require a click in order to open the submenu. // Cascading menus are opened with a mouse-over and // may be closed by a click. int clickCount = isMenuBarItem(menuItem) ? 1 : 0; Menu menu = getEventThreadQueuer().invokeAndWait( "openSubMenu", new IRunnable<Menu>() { //$NON-NLS-1$ public Menu run() { return menuItem.getMenu(); } }); Rectangle bounds = getMenuItemBounds(); Rectangle nullBounds = new Rectangle(0, 0, 0, 0); if (bounds.equals(nullBounds)) { openSubMenuProgramatically(menu); } else { clickMenuItem(getRobot(), menuItem, clickCount); } synchronized (lock) { long timeout = TimeoutConstants.SERVER_TIMEOUT_WAIT_FOR_POPUP; long done = System.currentTimeMillis() + timeout; long now; while (!lock.isReleased() && timeout > 0) { lock.wait(timeout); now = System.currentTimeMillis(); timeout = done - now; } } } catch (InterruptedException e) { // ignore } finally { queuer.invokeAndWait("removeMenuShownListeners", new IRunnable<Void>() { //$NON-NLS-1$ public Void run() { d.removeFilter(SWT.Show, listener); return null; } }); } if (!lock.isReleased()) { String itemText = getEventThreadQueuer().invokeAndWait( "getItemText", new IRunnable<String>() { //$NON-NLS-1$ public String run() throws StepExecutionException { if (menuItem != null && !menuItem.isDisposed()) { return CAPUtil.getWidgetText(menuItem, SwtUtils.removeMnemonics( menuItem.getText())); } return "unknown menu item"; //$NON-NLS-1$ } }); itemText = SwtUtils.removeMnemonics(itemText); throw new StepExecutionException( I18n.getString("TestErrorEvent.MenuDidNotAppear", //$NON-NLS-1$ new String [] {itemText}), EventFactory.createActionError( "TestErrorEvent.MenuDidNotAppear", //$NON-NLS-1$ new String [] {itemText})); } return new MenuAdapter(cond.getMenu()); } /** * @param menuItem the menu item to check * @return <code>true</code> of the given menu item is part of a menu * bar. Otherwise, <code>false</code>. */ private boolean isMenuBarItem(final MenuItem menuItem) { return getEventThreadQueuer().invokeAndWait( "isMenuBarItem", new IRunnable<Boolean>() { //$NON-NLS-1$ public Boolean run() throws StepExecutionException { if (menuItem != null && !menuItem.isDisposed()) { Menu parent = menuItem.getParent(); if (parent != null && !parent.isDisposed()) { return (parent.getStyle() & SWT.BAR) != 0; } } return false; } }); } /** * Waits for a submenu to appear. Examples of submenus are cascading menus * and pulldown menus. * * @author BREDEX GmbH * @created Oct 30, 2008 */ public static class MenuShownCondition implements Condition { /** the menu that was shown */ private Menu m_shownMenu = null; /** the parent item of the expected menu */ private MenuItem m_parentItem; /** * Constructor * * @param parentItem The parent item of the expected menu. This * condition only matches if a menu with parent item * <code>parentItem</code> appears. */ MenuShownCondition(MenuItem parentItem) { m_parentItem = parentItem; } /** * * @return the menu that appeared */ public Menu getMenu() { return m_shownMenu; } /** * * {@inheritDoc} */ public boolean isTrue(Event event) { if (event.type == SWT.Show && event.widget instanceof Menu && ((Menu)(event.widget)).getParentItem() == m_parentItem) { m_shownMenu = (Menu)event.widget; return true; } return false; } } /** * Clicks on a menu item * * @param robot the robot * @param item the menu item * @param clickCount the number of times to click the menu item */ private void clickMenuItem(IRobot robot, final MenuItem item, int clickCount) { boolean isSecondInMenu = getEventThreadQueuer() .invokeAndWait( "isMenuBar", new IRunnable<Boolean>() { //$NON-NLS-1$ public Boolean run() throws StepExecutionException { try { if ((item.getParent() .getParentMenu().getStyle() & SWT.BAR) != 0) { return Boolean.TRUE; } Menu parent = item.getMenu().getParentMenu(); if (parent != null) { Menu preparent = parent.getParentMenu(); if (preparent != null) { return (preparent.getStyle() & SWT.BAR) != 0; } } } catch (NullPointerException ne) { // Nothing here, there is no parent of parent. } return Boolean.FALSE; } }); if (isSecondInMenu) { robot.click(item, null, ClickOptions.create() .setClickType(ClickOptions.ClickType.RELEASED) .setStepMovement(true).setClickCount(clickCount) .setFirstHorizontal(false)); } else { robot.click(item, null, ClickOptions.create() .setClickType(ClickOptions.ClickType.RELEASED) .setStepMovement(true).setClickCount(clickCount)); } } /** * * @return bounds of MenuItem */ public Rectangle getMenuItemBounds() { return getEventThreadQueuer().invokeAndWait( "getMenuItemBounds", new IRunnable<Rectangle>() { //$NON-NLS-1$ public Rectangle run() { return SwtUtils.getBounds(m_menuItem); } }); } /** * open SubMenu programatically (for Mac OS) * @param menu the Menu */ public void openSubMenuProgramatically(final Menu menu) { if (!isMenuEnabled(menu)) { throw new StepExecutionException("menu item not enabled", //$NON-NLS-1$ EventFactory.createActionError( TestErrorEvent.MENU_ITEM_NOT_ENABLED)); } final InterceptorOptions options = new InterceptorOptions( new long[]{SWT.Show}); final IEventMatcher matcher = new ShowSwtEventMatcher(); RobotFactorySwtImpl robotSwt = new RobotFactorySwtImpl(); IRobotEventInterceptor interceptor = robotSwt.getRobotEventInterceptor(); final IRobotEventConfirmer confirmer = interceptor .intercept(options); final Event event = new Event(); event.time = (int) System.currentTimeMillis(); event.widget = menu; event.display = menu.getDisplay(); event.type = SWT.Show; getEventThreadQueuer().invokeAndWait( "openSubMenuProgramatically", new IRunnable<Void>() { //$NON-NLS-1$ public Void run() { menu.notifyListeners(SWT.Show, event); return null; } }); try { confirmer.waitToConfirm(menu, matcher); } catch (RobotException re) { final StringBuffer sb = new StringBuffer( "Robot exception occurred while clicking...\n"); //$NON-NLS-1$ sb.append("Component: "); //$NON-NLS-1$ getEventThreadQueuer().invokeAndWait( "getBounds", new IRunnable<Void>() { //$NON-NLS-1$ public Void run() throws StepExecutionException { sb.append(menu); // Return value not used return null; } }); log.error(sb.toString(), re); throw re; } } /** * select MenuItem programatically (for Mac OS) */ public void selectProgramatically() { if (!isMenuItemEnabled(m_menuItem)) { throw new StepExecutionException("menu item not enabled", //$NON-NLS-1$ EventFactory.createActionError( TestErrorEvent.MENU_ITEM_NOT_ENABLED)); } final MenuItem menuItem = m_menuItem; final InterceptorOptions options = new InterceptorOptions( new long[]{SWT.Selection}); final IEventMatcher matcher = new SelectionSwtEventMatcher(); RobotFactorySwtImpl robotSwt = new RobotFactorySwtImpl(); IRobotEventInterceptor interceptor = robotSwt.getRobotEventInterceptor(); final IRobotEventConfirmer confirmer = interceptor .intercept(options); final Event event = new Event(); event.time = (int) System.currentTimeMillis(); event.widget = menuItem; event.display = menuItem.getDisplay(); event.type = SWT.Selection; closeUnderMac(); getEventThreadQueuer().invokeLater( "selectProgramatically", new Runnable() { //$NON-NLS-1$ public void run() { //if menuitem is checkbox or radiobutton set Selection if ((menuItem.getStyle() & SWT.CHECK) == 0 || (menuItem.getStyle() & SWT.RADIO) == 0) { if (menuItem.getSelection()) { menuItem.setSelection(false); } else { menuItem.setSelection(true); } } menuItem.notifyListeners(SWT.Selection, event); } }); try { confirmer.waitToConfirm(menuItem, matcher); } catch (RobotException re) { final StringBuffer sb = new StringBuffer( "Robot exception occurred while clicking...\n"); //$NON-NLS-1$ //logRobotException(menuItem, re, sb); sb.append("Component: "); //$NON-NLS-1$ getEventThreadQueuer().invokeAndWait( "getBounds", new IRunnable<Void>() { //$NON-NLS-1$ public Void run() throws StepExecutionException { sb.append(menuItem); // Return value not used return null; } }); log.error(sb.toString(), re); throw re; } } /** * "close" (hide) the context menu. this is necessary because if you * select programatically the contextmenu is not closed. */ private void closeUnderMac() { if (EnvironmentUtils.isMacOS()) { // "close" (hide) the context menu. this is necessary because // the selection event will not close the context menu. // we do this before firing the selection event so that the menu // disappears before the effects of the selection event (e.g. // showing a dialog) are presented. m_menuItem.getDisplay().syncExec(new Runnable() { public void run() { Menu parentMenu = m_menuItem.getParent(); while (parentMenu.getParentMenu() != null) { parentMenu = parentMenu.getParentMenu(); } parentMenu.setVisible(false); } }); } } /** * Calls MenuItem.isEnabled() in the GUI-Thread * @param menuItem the MenuItem * @return true if enabled, false otherwise * @see MenuItem#isEnabled() */ private boolean isMenuItemEnabled(final MenuItem menuItem) { return getEventThreadQueuer().invokeAndWait( MenuItemAdapter.class + ".isMenuItemEnabled", new IRunnable<Boolean>() { //$NON-NLS-1$ public Boolean run() throws StepExecutionException { return menuItem.isEnabled(); } }); } /** * Calls MenuItem.isEnabled() in the GUI-Thread * @param menu the Menu * @return true if enabled, false otherwise * @see MenuItem#isEnabled() */ private boolean isMenuEnabled(final Menu menu) { return getEventThreadQueuer().invokeAndWait( MenuItemAdapter.class + ".isMenuEnabled", new IRunnable<Boolean>() { //$NON-NLS-1$ public Boolean run() throws StepExecutionException { return menu.isEnabled(); } }); } }