/******************************************************************************* * 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; import static org.eclipse.jubula.rc.common.driver.CheckWithTimeoutQueuer.invokeAndWait; import java.awt.Rectangle; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; import org.eclipse.jubula.rc.common.AUTServer; import org.eclipse.jubula.rc.common.driver.IEventThreadQueuer; import org.eclipse.jubula.rc.common.driver.IRobot; import org.eclipse.jubula.rc.common.driver.IRobotFactory; import org.eclipse.jubula.rc.common.driver.IRunnable; 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.AbstractApplicationTester; import org.eclipse.jubula.rc.common.util.KeyStrokeUtil; import org.eclipse.jubula.rc.common.util.MatchUtil; import org.eclipse.jubula.rc.common.util.Verifier; import org.eclipse.jubula.rc.common.util.WorkaroundUtil; import org.eclipse.jubula.rc.swt.SwtAUTServer; import org.eclipse.jubula.rc.swt.components.SwtComponent; import org.eclipse.jubula.rc.swt.driver.EventThreadQueuerSwtImpl; import org.eclipse.jubula.rc.swt.driver.RobotFactoryConfig; import org.eclipse.jubula.rc.swt.listener.ComponentHandler; import org.eclipse.jubula.rc.swt.listener.FocusTracker; 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.SwtPointUtil; 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.jubula.tools.internal.utils.TimeUtil; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Widget; /** * @author BREDEX GmbH */ public class SwtApplicationTester extends AbstractApplicationTester { /** The logging. */ private static AutServerLogger log = new AutServerLogger(SwtApplicationTester.class); /** * This condition is true if the event source is a Shell with a matching * title. * * @author BREDEX GmbH * @created Jun 17, 2009 */ private static class WindowEventCondition implements Condition { /** the expected window title */ private String m_windowTitle; /** the operator used for matching the window title */ private String m_matchingOperator; /** * determines whether the event source being disposed should be * treated as a match */ private boolean m_valForDisposed; /** * Constructor * * @param windowTitle The expected window title. * @param matchingOperator The operator used for matching the * window title. * @param valForDisposed Whether the event source being disposed * should be treated as a match. */ public WindowEventCondition(String windowTitle, String matchingOperator, boolean valForDisposed) { m_windowTitle = windowTitle; m_matchingOperator = matchingOperator; m_valForDisposed = valForDisposed; } /** * {@inheritDoc} */ public boolean isTrue(Event event) { if (event.widget instanceof Shell) { Shell window = (Shell)event.widget; if (window.isDisposed()) { return m_valForDisposed; } String windowText = CAPUtil.getWidgetText( window, window.getText()); return MatchUtil.getInstance().match(windowText, m_windowTitle, m_matchingOperator); } return false; } } /** The Robot factory. */ private IRobotFactory m_robotFactory; /** * @return The Robot factory instance */ private IRobotFactory getRobotFactory() { if (m_robotFactory == null) { m_robotFactory = new RobotFactoryConfig().getRobotFactory(); } return m_robotFactory; } /** * {@inheritDoc} */ public String[] getTextArrayFromComponent() { return null; } /** * Waits <code>timeMillSec</code> if the application opens a window with the given title. * @param title the title * @param operator the comparing operator * @param timeout the time in ms * @param delay delay after the window is shown */ public void rcWaitForWindow(final String title, final String operator, int timeout, int delay) { final EventListener.Condition cond = new WindowEventCondition(title, operator, false); final EventLock lock = new EventLock(); final Listener listener = new EventListener(lock, cond); final Display display = ((SwtAUTServer)AUTServer.getInstance()).getAutDisplay(); final IEventThreadQueuer queuer = new EventThreadQueuerSwtImpl(); queuer.invokeAndWait("addWindowOpenedListeners", new IRunnable<Void>() { //$NON-NLS-1$ public Void run() { display.addFilter(SWT.Activate, listener); display.addFilter(SWT.Show, listener); if (isWindowOpen(title, operator)) { lock.release(); } return null; } }); try { synchronized (lock) { long currentTimeout = timeout; long done = System.currentTimeMillis() + timeout; long now; while (!lock.isReleased() && (currentTimeout > 0)) { try { lock.wait(currentTimeout); now = System.currentTimeMillis(); currentTimeout = done - now; } catch (InterruptedException e) { // ignore } } } } finally { queuer.invokeAndWait("removeWindowOpenedListeners", new IRunnable<Void>() { //$NON-NLS-1$ public Void run() { display.removeFilter(SWT.Activate, listener); display.removeFilter(SWT.Show, listener); return null; } }); } if (!lock.isReleased()) { throw new StepExecutionException("window did not open", //$NON-NLS-1$ EventFactory.createActionError( TestErrorEvent.TIMEOUT_EXPIRED)); } TimeUtil.delay(delay); } /** * Waits <code>timeMillSec</code> if the application activates a window * with the given title. * * @param title the title * @param operator the comparing operator * @param timeout the time in ms * @param delay delay after the window is activated */ public void rcWaitForWindowActivation(final String title, final String operator, final int timeout, int delay) { final EventListener.Condition cond = new WindowEventCondition(title, operator, false); final EventLock lock = new EventLock(); final Listener listener = new EventListener(lock, cond); final Display display = ((SwtAUTServer)AUTServer.getInstance()).getAutDisplay(); final IEventThreadQueuer queuer = new EventThreadQueuerSwtImpl(); queuer.invokeAndWait("addWindowActiveListeners", new IRunnable<Void>() { //$NON-NLS-1$ public Void run() { display.addFilter(SWT.Activate, listener); if (isWindowActive(title, operator)) { lock.release(); } return null; } }); try { synchronized (lock) { long currentTimeout = timeout; long done = System.currentTimeMillis() + timeout; long now; while (!lock.isReleased() && (currentTimeout > 0)) { try { lock.wait(currentTimeout); now = System.currentTimeMillis(); currentTimeout = done - now; } catch (InterruptedException e) { // ignore } } } } finally { queuer.invokeAndWait("removeWindowActiveListeners", new IRunnable<Void>() { //$NON-NLS-1$ public Void run() { display.removeFilter(SWT.Activate, listener); return null; } }); } if (!lock.isReleased()) { throw new StepExecutionException("window was not activated", //$NON-NLS-1$ EventFactory.createActionError( TestErrorEvent.TIMEOUT_EXPIRED)); } TimeUtil.delay(delay); } /** * Waits <code>timeMillSec</code> if the application closes (or hides) * a window with the given title. If no window with the given title can * be found, then it is assumed that the window has already closed. * * @param title the title * @param operator the comparing operator * @param timeout the time in ms * @param delay delay after the window is activated */ public void rcWaitForWindowToClose(final String title, final String operator, int timeout, int delay) { final EventListener.Condition cond = new WindowEventCondition(title, operator, true); final EventLock lock = new EventLock(); final Listener listener = new EventListener(lock, cond); final Display display = ((SwtAUTServer)AUTServer.getInstance()).getAutDisplay(); final IEventThreadQueuer queuer = new EventThreadQueuerSwtImpl(); queuer.invokeAndWait("addWindowClosedListeners", new IRunnable<Void>() { //$NON-NLS-1$ public Void run() { display.addFilter(SWT.Close, listener); display.addFilter(SWT.Hide, listener); display.addFilter(SWT.Dispose, listener); if (!isWindowOpen(title, operator)) { lock.release(); } return null; } }); try { synchronized (lock) { long currentTimeout = timeout; long done = System.currentTimeMillis() + timeout; long now; while (!lock.isReleased() && (currentTimeout > 0)) { try { lock.wait(currentTimeout); now = System.currentTimeMillis(); currentTimeout = done - now; } catch (InterruptedException e) { // ignore } } } } finally { queuer.invokeAndWait("removeWindowClosedListeners", new IRunnable<Void>() { //$NON-NLS-1$ public Void run() { display.removeFilter(SWT.Close, listener); display.removeFilter(SWT.Hide, listener); display.removeFilter(SWT.Dispose, listener); return null; } }); } if (!lock.isReleased()) { throw new StepExecutionException("window did not close", //$NON-NLS-1$ EventFactory.createActionError( TestErrorEvent.TIMEOUT_EXPIRED)); } TimeUtil.delay(delay); } /** * Returns <code>true</code> if a window with the given title is open and * visible * * @param title the title * @param operator the matches/equals operator * @return if the window is open and visible */ private boolean isWindowOpen(final String title, final String operator) { boolean wasInterrupted = false; boolean equal = false; do { try { wasInterrupted = false; Collection components = ComponentHandler .getAutHierarchy().getHierarchyMap() .keySet(); for (Iterator it = components.iterator(); it.hasNext();) { Widget comp = ((SwtComponent)it.next()).getComponent(); if (comp instanceof Shell && !comp.isDisposed() && ((Shell)comp).isVisible()) { Shell frame = (Shell)comp; if (MatchUtil.getInstance().match( CAPUtil.getWidgetText(frame, frame.getText()), title, operator)) { equal = true; break; } } } } catch (ConcurrentModificationException e) { log.debug("hierarchy modified while traversing", e); //$NON-NLS-1$ wasInterrupted = true; } } while (wasInterrupted); return equal; } /** * Checks for the existence of a window with the given title * * @param title * the title * @param operator * the comparing operator * @param exists * <code>True</code> if the component is expected to exist and be * visible, otherwise <code>false</code>. * @param timeout the amount of time to wait for the existence of the * window to be checked */ public void rcCheckExistenceOfWindow(final String title, final String operator, final boolean exists, int timeout) { final IEventThreadQueuer queuer = new EventThreadQueuerSwtImpl(); invokeAndWait("rcCheckExistenceOfWindow", timeout, new Runnable() { //$NON-NLS-1$ public void run() { Boolean windowExists = queuer.invokeAndWait( "isWindowOpen", new IRunnable<Boolean>() { //$NON-NLS-1$ public Boolean run() throws StepExecutionException { return isWindowOpen(title, operator); } }); Verifier.equals(exists, windowExists.booleanValue()); } }); } /** * {@inheritDoc} */ public Rectangle getActiveWindowBounds() { org.eclipse.swt.graphics.Rectangle activeWindowSize = getRobotFactory() .getEventThreadQueuer().invokeAndWait( this.getClass().getName() + ".getActiveWindowBounds", //$NON-NLS-1$ new IRunnable<org.eclipse.swt.graphics.Rectangle>() { // SYNCH THREAD START public org.eclipse.swt.graphics.Rectangle run() { Display d = ((SwtAUTServer)AUTServer .getInstance()).getAutDisplay(); if (d != null && d.getActiveShell() != null) { return d.getActiveShell().getBounds(); } return null; } }); if (activeWindowSize != null) { return SwtPointUtil.toAwtRectangle(activeWindowSize); } return null; } /** * {@inheritDoc} */ protected IRobot getRobot() { return AUTServer.getInstance().getRobot(); } /** * perform a keystroke * @param modifierSpec the string representation of the modifiers * @param keySpec the string representation of the key */ public void rcKeyStroke(String modifierSpec, String keySpec) { if (keySpec == null || keySpec.trim().length() == 0) { throw new StepExecutionException( "The base key of the key stroke must not be null or empty", //$NON-NLS-1$ EventFactory.createActionError( TestErrorEvent.INVALID_PARAM_VALUE)); } String keyStrokeSpec = keySpec.trim(); String mod = KeyStrokeUtil.getModifierString(modifierSpec); if (mod.length() > 0) { keyStrokeSpec = mod + " " + keyStrokeSpec; //$NON-NLS-1$ } String keySpecification = keySpec.trim().toLowerCase(); if (EnvironmentUtils.isMacOS() && keySpecification.length() == 1 && keySpecification.charAt(0) == WorkaroundUtil.CHAR_B) { rcNativeKeyStroke(modifierSpec, keySpec); } else { // at this the key stroke specification is not fully fulfilled as the // key stroke spec base key is not definitely upper case getRobot().keyStroke(keyStrokeSpec); } } /** * {@inheritDoc} */ protected Object getFocusOwner() { return FocusTracker.getFocusOwner(); } /** * {@inheritDoc} */ protected int getEventCode(int key) { int event = 0; switch (key) { case 1 : event = SWT.NUM_LOCK; break; case 2 : event = SWT.CAPS_LOCK; break; case 3 : event = SWT.SCROLL_LOCK; break; default : break; } return event; } /** * {@inheritDoc} */ protected Object getActiveWindow() { return getRobotFactory().getEventThreadQueuer() .invokeAndWait(this.getClass().getName() + ".getActiveWindow", //$NON-NLS-1$ new IRunnable<Shell>() { public Shell run() { // SYNCH THREAD START Display d = ((SwtAUTServer) AUTServer .getInstance()).getAutDisplay(); return d.getActiveShell(); } } ); } /** * Returns <code>true</code> if a window with the given title is active * (the window with focus). * * @param title the title * @param operator the matches/equals operator * @return if the window is open and visible */ private boolean isWindowActive(final String title, final String operator) { final Shell activeWindow = (Shell) getActiveWindow(); if (activeWindow == null) { if (log.isWarnEnabled()) { log.warn("No active Window found while searching for Window with title: '" //$NON-NLS-1$ + String.valueOf(title) + "'! " + //$NON-NLS-1$ "(SwtApplicationImplClass#isWindowActive(String, String))"); //$NON-NLS-1$ } return false; } final String windowTitle = CAPUtil.getWidgetText(activeWindow, activeWindow.getText()); return MatchUtil.getInstance().match(windowTitle, title, operator); } }