diff options
| author | Markus Tiede | 2013-01-04 09:01:32 +0000 |
|---|---|---|
| committer | Markus Tiede | 2013-01-04 09:01:32 +0000 |
| commit | bd45bc8860028ca7010b772532e8458a11ba30ce (patch) | |
| tree | db27a6a023ad17ca4b2a1beb1344e6cc480a551d | |
| parent | ee9ce223396c5bd5232aa31a9912849b35979750 (diff) | |
| download | org.eclipse.jubula.core-bd45bc8860028ca7010b772532e8458a11ba30ce.tar.gz org.eclipse.jubula.core-bd45bc8860028ca7010b772532e8458a11ba30ce.tar.xz org.eclipse.jubula.core-bd45bc8860028ca7010b772532e8458a11ba30ce.zip | |
Sprint task - second patch 'application refactoring' for enhancement 394179 applied.
5 files changed, 1876 insertions, 2 deletions
diff --git a/org.eclipse.jubula.rc.common/src/org/eclipse/jubula/rc/common/caps/AbstractApplicationCAPs.java b/org.eclipse.jubula.rc.common/src/org/eclipse/jubula/rc/common/caps/AbstractApplicationCAPs.java new file mode 100644 index 000000000..ce7b602d1 --- /dev/null +++ b/org.eclipse.jubula.rc.common/src/org/eclipse/jubula/rc/common/caps/AbstractApplicationCAPs.java @@ -0,0 +1,749 @@ +/******************************************************************************* + * 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.common.caps; + +import java.awt.AWTException; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import org.eclipse.jubula.rc.common.driver.ClickOptions; +import org.eclipse.jubula.rc.common.driver.IRobot; +import org.eclipse.jubula.rc.common.driver.KeyTyper; +import org.eclipse.jubula.rc.common.exception.ExecutionEvent; +import org.eclipse.jubula.rc.common.exception.OsNotSupportedException; +import org.eclipse.jubula.rc.common.exception.RobotException; +import org.eclipse.jubula.rc.common.exception.StepExecutionException; +import org.eclipse.jubula.rc.common.implclasses.Comparer; +import org.eclipse.jubula.rc.common.implclasses.IBaseImplementationClass; +import org.eclipse.jubula.rc.common.implclasses.Verifier; +import org.eclipse.jubula.rc.common.logger.AutServerLogger; +import org.eclipse.jubula.rc.common.util.KeyStrokeUtil; +import org.eclipse.jubula.tools.constants.StringConstants; +import org.eclipse.jubula.tools.objects.event.EventFactory; +import org.eclipse.jubula.tools.objects.event.TestErrorEvent; +import org.eclipse.jubula.tools.utils.ExternalCommandExecutor; +import org.eclipse.jubula.tools.utils.TimeUtil; +import org.eclipse.jubula.tools.utils.ExternalCommandExecutor.MonitorTask; +/** + * + * @author BREDEX GmbH + * + */ +public abstract class AbstractApplicationCAPs + implements IBaseImplementationClass { + /** + * String for sequential numbering for screenshots + */ + public static final String RENAME = "rename"; //$NON-NLS-1$ + + /** + * String for overwriting for screenshots + */ + public static final String OVERWRITE = "overwrite"; //$NON-NLS-1$ + + /** + * The default format to use when writing images to disk. + */ + private static final String DEFAULT_IMAGE_FORMAT = "png"; //$NON-NLS-1$ + + /** + * The string used to separate filename and file extension. + */ + private static final String EXTENSION_SEPARATOR = "."; //$NON-NLS-1$ + + /** constants for communication */ + private static final String POS_UNIT_PIXEL = "Pixel"; //$NON-NLS-1$ + + /** constants for communication */ + private static final String POS_UNI_PERCENT = "Percent"; //$NON-NLS-1$ + + /** + * The logging. + */ + private static AutServerLogger log = + new AutServerLogger(AbstractApplicationCAPs.class); + + /** + * @param text text to type + */ + public void gdInputText(String text) { + getRobot().type(getFocusOwner(), text); + } + + /** + * Executes the given command and waits for it to finish. If the + * execution does not finish in good time, a timeout will occur. If the + * exit code for the execution is not the same as the expected code, the + * test step fails. + * + * @param cmd The command to execute. + * @param expectedExitCode The expected exit code of the command. + * @param local <code>true</code> if the command should be executed on the + * local (client) machine. Otherwise (should run on the server side), + * this value should be <code>false</code>. + * @param timeout The amount of time (in milliseconds) to wait for the + * execution to finish. + */ + public void gdExecuteExternalCommand(String cmd, int expectedExitCode, + boolean local, int timeout) { + + if (!local) { + MonitorTask mt = new ExternalCommandExecutor().executeCommand( + null, cmd, timeout); + + if (!mt.wasCmdValid()) { + throw new StepExecutionException( + "Command not found.", //$NON-NLS-1$ + EventFactory.createActionError( + TestErrorEvent.NO_SUCH_COMMAND)); + } + + if (mt.hasTimeoutOccurred()) { + throw new StepExecutionException( + "Timeout received before completing execution of script.", //$NON-NLS-1$ + EventFactory.createActionError( + TestErrorEvent.CONFIRMATION_TIMEOUT)); + } + + int actualExitValue = mt.getExitCode(); + if (actualExitValue != expectedExitCode) { + throw new StepExecutionException( + "Verification of exit code failed.", //$NON-NLS-1$ + EventFactory.createVerifyFailed( + String.valueOf(expectedExitCode), + String.valueOf(actualExitValue))); + } + } + + } + + /** + * {@inheritDoc} + */ + public void setComponent(Object graphicsComponent) { + // Do nothing; Application has no correspaonding component + } + + /** + * Takes a screenshot and saves the image to disk. + * + * @param destination + * Path and filename for the created image. If the extension is not + * ".jpeg" (case-insensitive), ".jpeg" will be appended to the + * filename. + * @param delay + * Amount of time to wait (in milliseconds) before taking the + * screenshot. + * @param fileAccess + * Determines how the file will be created if a file with the + * given name and path already exists:<br> + * <code>SwingApplicationImplClass.RENAME</code> - + * The screenshot will be saved with a sequential integer appended + * to the filename.<br> + * <code>SwingApplicationImplClass.OVERWRITE</code> - + * The screenshot will overwrite the file. + * @param scaling + * Degree to which the image should be scaled, in percent. A + * <code>scaling</code> value of <code>100</code> produces an + * unscaled image. This value must be greater than <code>0</code> + * and less than or equal to <code>200</code>. + * @param createDirs + * Determines whether a path will be created if it does not already + * exist. A value of <code>true</code> means that all necessary + * directories that do not exist will be created automatically. + */ + public void gdTakeScreenshot(String destination, int delay, + String fileAccess, int scaling, boolean createDirs) { + // Determine current screen size + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Dimension screenSize = toolkit.getScreenSize(); + + // If screen !(resolution%2==0) --> bad scaling + int screenWidth = (int)screenSize.getWidth(); + int screenHeight = (int)screenSize.getHeight(); + if (!(screenWidth % 2 == 0)) { + screenWidth = screenWidth - 1; + } + if (!(screenHeight % 2 == 0)) { + screenHeight = screenHeight - 1; + } + + screenSize.setSize(screenWidth, screenHeight); + + Rectangle screenRect = new Rectangle(screenSize); + + takeScreenshot(destination, delay, fileAccess, scaling, createDirs, + screenRect); + } + + /** + * Takes a screenshot and saves the image to disk. + * + * @param destination + * Path and filename for the created image. If the extension is + * not ".jpeg" (case-insensitive), ".jpeg" will be appended to + * the filename. + * @param delay + * Amount of time to wait (in milliseconds) before taking the + * screenshot. + * @param fileAccess + * Determines how the file will be created if a file with the + * given name and path already exists:<br> + * <code>SwingApplicationImplClass.RENAME</code> - The screenshot + * will be saved with a sequential integer appended to the + * filename.<br> + * <code>SwingApplicationImplClass.OVERWRITE</code> - The + * screenshot will overwrite the file. + * @param scaling + * Degree to which the image should be scaled, in percent. A + * <code>scaling</code> value of <code>100</code> produces an + * unscaled image. This value must be greater than <code>0</code> + * and less than or equal to <code>200</code>. + * @param createDirs + * Determines whether a path will be created if it does not + * already exist. A value of <code>true</code> means that all + * necessary directories that do not exist will be created + * automatically. + * @param marginTop + * the extra top margin + * @param marginRight + * the extra right margin + * @param marginBottom + * the extra bottom margin + * @param marginLeft + * the extra left margin + */ + public void gdTakeScreenshotOfActiveWindow(String destination, int delay, + String fileAccess, int scaling, boolean createDirs, int marginTop, + int marginRight, int marginBottom, int marginLeft) { + Rectangle activeWindowBounds = getActiveWindowBounds(); + + if (activeWindowBounds == null) { + throw new StepExecutionException("No active window found", //$NON-NLS-1$ + EventFactory + .createActionError(TestErrorEvent.NO_ACTIVE_WINDOW)); + } + + int x = activeWindowBounds.x - marginLeft; + int y = activeWindowBounds.y - marginTop; + int width = activeWindowBounds.width + marginLeft + marginRight; + int height = activeWindowBounds.height + marginTop + marginBottom; + + if (width < 1 || height < 1) { + throw new StepExecutionException("Margin parameter lead to negative height or width", //$NON-NLS-1$ + EventFactory + .createActionError(TestErrorEvent.INVALID_INPUT)); + } + + Rectangle screenRect = new Rectangle(x, y, width, height); + + takeScreenshot(destination, delay, fileAccess, scaling, createDirs, + screenRect); + } + + /** + * @return an awt rectangle which represents the absolute active window + * bounds; may return null e.g. if no active window could be found + */ + public abstract Rectangle getActiveWindowBounds(); + + + /** + * Takes a screenshot and saves the image to disk, in JPEG format. + * + * @param destination + * Path and filename for the created image. If the extension is not + * ".jpeg" (case-insensitive), ".jpeg" will be appended to the + * filename. + * @param delay + * Amount of time to wait (in milliseconds) before taking the + * screenshot. + * @param fileAccess + * Determines how the file will be created if a file with the + * given name and path already exists:<br> + * <code>SwingApplicationImplClass.RENAME</code> - + * The screenshot will be saved with a sequential integer appended + * to the filename.<br> + * <code>SwingApplicationImplClass.OVERWRITE</code> - + * The screenshot will overwrite the file. + * @param scaling + * Degree to which the image should be scaled, in percent. A + * <code>scaling</code> value of <code>100</code> produces an + * unscaled image. This value must be greater than <code>0</code> + * and less than or equal to <code>200</code>. + * @param createDirs + * Determines whether a path will be created if it does not already + * exist. A value of <code>true</code> means that all necessary + * directories that do not exist will be created automatically. + * @param screenShotRect + * the rectangle to take the screenshot of + */ + private void takeScreenshot(String destination, int delay, + String fileAccess, int scaling, boolean createDirs, + Rectangle screenShotRect) { + if (scaling <= 0 || scaling > 200) { + throw new StepExecutionException( + "Invalid scaling factor: Must be between 1 and 200", //$NON-NLS-1$ + EventFactory.createActionError( + TestErrorEvent.INVALID_PARAM_VALUE)); + } + + double scaleFactor = scaling * 0.01; + + // Check if file name is valid + String outFileName = destination; + String imageExtension = getExtension(outFileName); + if (imageExtension.length() == 0) { + // If not, then we simply append the default extension + imageExtension = DEFAULT_IMAGE_FORMAT; + outFileName += EXTENSION_SEPARATOR + imageExtension; + } + + // Wait for a user-specified time + if (delay > 0) { + TimeUtil.delay(delay); + } + + // Create path, if necessary + File pic = new File(outFileName); + if (pic.getParent() == null) { + throw new StepExecutionException( + "Invalid file name: specify a file name", //$NON-NLS-1$ + EventFactory.createActionError( + TestErrorEvent.INVALID_PARAM_VALUE)); + } + + File path = new File(pic.getParent()); + if (createDirs && !path.exists() && !path.mkdirs()) { + throw new StepExecutionException( + "Directory path does not exist and could not be created", //$NON-NLS-1$ + EventFactory.createActionError( + TestErrorEvent.FILE_IO_ERROR)); + } + + // Rename file if file already exists + // FIXME zeb This naming scheme can lead to sorting problems when + // filenames have varying numbers of digits (ex. "pic_9" and + // "pic_10") + if (fileAccess.equals(RENAME)) { + String completeExtension = + EXTENSION_SEPARATOR + imageExtension.toLowerCase(); + int extensionIndex = + pic.getName().toLowerCase().lastIndexOf(completeExtension); + String fileName = pic.getName().substring(0, extensionIndex); + for (int i = 1; pic.exists(); i++) { + pic = new File(pic.getParent(), fileName + "_" + i + completeExtension); //$NON-NLS-1$ + } + } + + takeScreenshot(screenShotRect, scaleFactor, pic); + } + + /** + * Takes a screenshot and saves the image to disk. This method will attempt + * to encode the image according to the file extension of the given + * output file. If this is not possible (because the encoding type + * is not supported), then the default encoding type will be used. If + * the default encoding type is used, an appropriate extension will be added + * to the filename. + * + * @param captureRect + * Rect to capture in screen coordinates. + * @param scaleFactor + * Degree to which the image should be scaled, in percent. A + * <code>scaleFactor</code> of <code>100</code> produces an + * unscaled image. This value must be greater than <code>0</code> + * and less than or equal to <code>200</code>. + * @param outputFile + * Path and filename for the created image. + */ + public void takeScreenshot( + Rectangle captureRect, double scaleFactor, File outputFile) { + // Create screenshot + java.awt.Robot robot; + File out = outputFile; + + try { + robot = new java.awt.Robot(); + BufferedImage image = robot.createScreenCapture(captureRect); + + int scaledWidth = (int) Math.floor(image.getWidth() * scaleFactor); + int scaledHeight = + (int) Math.floor(image.getHeight() * scaleFactor); + BufferedImage imageOut = + new BufferedImage(scaledWidth, + scaledHeight, BufferedImage.TYPE_INT_RGB); + // Scale it to the new size on-the-fly + Graphics2D graphics2D = imageOut.createGraphics(); + graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + graphics2D.drawImage(image, 0, 0, scaledWidth, scaledHeight, null); + + // Save captured image using given format, if supported. + String extension = getExtension(out.getName()); + if (extension.length() == 0 + || !ImageIO.getImageWritersBySuffix(extension).hasNext() + || !ImageIO.write(imageOut, extension, out)) { + + // Otherwise, save using default format + out = new File(outputFile.getPath() + + EXTENSION_SEPARATOR + DEFAULT_IMAGE_FORMAT); + if (!ImageIO.write(imageOut, DEFAULT_IMAGE_FORMAT, out)) { + + // This should never happen, so log as error if it does. + // In this situation, the screenshot will not be saved, but + // the test step will still be marked as successful. + log.error("Screenshot could not be saved. " + //$NON-NLS-1$ + "Default image format (" + DEFAULT_IMAGE_FORMAT //$NON-NLS-1$ + + ") is not supported."); //$NON-NLS-1$ + } + } + + } catch (AWTException e) { + throw new RobotException(e); + } catch (IOException e) { + throw new StepExecutionException( + "Screenshot could not be saved", //$NON-NLS-1$ + EventFactory.createActionError( + TestErrorEvent.FILE_IO_ERROR)); + } + } + + /** + * Waits a specified time. + * @param timeMilliSec the time to wait in MilliSec + */ + public void gdWait(int timeMilliSec) { + TimeUtil.delay(timeMilliSec); + } + + /** + * shows a ConfirmDialog and Pause the Execution of the Test until Window + * is closed + */ + public void gdPause() { + throw new ExecutionEvent(ExecutionEvent.PAUSE_EXECUTION); + } + + /** + * Does nothing! The restart is implemented in the client but the server + * must have an action to execute. + */ + public void gdRestart() { + // nothing + } + + /** + * Types the given text without checking location or event confirmation. + * + * @param text The text to type. + */ + public void gdNativeInputText(String text) { + try { + KeyTyper.getInstance().nativeTypeString(text); + } catch (AWTException e) { + throw new RobotException(e); + } + } + + /** + * Action to perform a manual test step on server side; opens a window and + * wait's for real user interaction + * + * @param actionToPerform + * a textual description of the action to perform in the AUT + * @param expectedBehavior + * a textual description of the expected behaviour + * @param timeout + * the timeout + */ + public void gdManualTestStep(String actionToPerform, + String expectedBehavior, int timeout) { + // empty implementation: implementation can be found in the corresponding + // postExecutionCommand + } + + /** + * Perform a keystroke specified according <a + * href=http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/KeyStroke.html#getKeyStroke(java.lang.String)> + * string representation of a keystroke </a>. + * This method does not wait for event confirmation, as we have no way of + * confirming events on OS-native widgets. + * + * @param modifierSpec the string representation of the modifiers + * @param keySpec the string representation of the key + */ + public void gdNativeKeyStroke(String modifierSpec, String keySpec) { + if (keySpec == null || keySpec.trim().length() == 0) { + throw new StepExecutionException("The base key of the key stroke " //$NON-NLS-1$ + + "must not be null or empty", //$NON-NLS-1$ + EventFactory.createActionError()); + } + + try { + + KeyTyper typer = KeyTyper.getInstance(); + String keyStrokeSpec = keySpec.trim().toUpperCase(); + String mod = KeyStrokeUtil.getModifierString(modifierSpec); + if (mod.length() > 0) { + keyStrokeSpec = mod + " " + keyStrokeSpec; //$NON-NLS-1$ + } + + typer.type(keyStrokeSpec, null, null, null); + + } catch (AWTException e) { + throw new RobotException(e); + } + + } + + /** + * Action to set the value of a variable in the Client. + * + * @param variable The name of the variable. + * @param value The new value for the variable. + * @return the new value for the variable. + */ + public String gdSetValue(String variable, String value) { + return value; + } + + /** + * @return The Robot instance + */ + protected abstract IRobot getRobot(); + + /** + * + * @param filename A filename for which to find the extension. + * @return the file extension for the given filename. For example, + * "png" for "example.png". Returns an empty string if the given + * name has no extension. This is the case if the given name does + * not contain an instance of the extension separator or ends with + * the extension separator. For example, "example" or "example.". + */ + private String getExtension(String filename) { + File file = new File(filename); + int extensionIndex = file.getName().lastIndexOf(EXTENSION_SEPARATOR); + return extensionIndex == -1 + || extensionIndex == file.getName().length() - 1 + ? StringConstants.EMPTY + : file.getName().substring(extensionIndex + 1); + } + + /** + * method to copy a string to the system clipboard + * + * @param text The text to copy + */ + public void gdCopyToClipboard(final String text) { + StringSelection strSel = new StringSelection(text); + try { + Toolkit.getDefaultToolkit().getSystemClipboard() + .setContents(strSel, null); + } catch (IllegalStateException ise) { + throw new StepExecutionException( + "Clipboard not available.", //$NON-NLS-1$ + EventFactory.createActionError( + TestErrorEvent.CLIPBOARD_NOT_AVAILABLE)); + } + } + + /** + * method to compare to values + * + * @param value1 + * the first value for comparison + * @param comparisonMethod + * the comparison method + * @param value2 + * the second value for comparison + */ + public void gdCheckValues(final String value1, + final String comparisonMethod, final String value2) { + Comparer.compare(value1, value2, comparisonMethod); + } + + /** + * method to compare to strings + * + * @param value1 + * the first value for comparison + * @param operator + * the comparison method + * @param value2 + * the second value for comparison + */ + public void gdCheckStringValues(final String value1, + final String operator, final String value2) { + Verifier.match(value1, value2, operator); + } + + /** + * Does nothing! The start timer is implemented in the client but the server + * must have an action to execute. + * @param timerName the name for the timer + * @param variableName the variable name to store the current time in millisecs in + */ + public void gdStartTimer(String timerName, String variableName) { + // empty + } + + /** + * Does nothing! The read timer is implemented in the client but the server + * must have an action to execute. + * @param timerName the name for the timer + * @param variableName the variable name to store the current time delta in millisecs in + */ + public void gdReadTimer(String timerName, String variableName) { + // empty + } + + /** + * activate the AUT + * + * @param method activation method + */ + public void gdActivate(String method) { + getRobot().activateApplication(method); + } + + /** + * clicks into the active window. + * + * @param count amount of clicks + * @param button what mouse button should be used + * @param xPos what x position + * @param xUnits should x position be pixel or percent values + * @param yPos what y position + * @param yUnits should y position be pixel or percent values + * @throws StepExecutionException error + */ + public void gdClickDirect(int count, int button, + int xPos, String xUnits, int yPos, String yUnits) + throws StepExecutionException { + + Object activeWindow = getActiveWindow(); + if (activeWindow != null) { + getRobot().click(activeWindow, null, + ClickOptions.create() + .setClickCount(count) + .setConfirmClick(false) + .setMouseButton(button), + xPos, + xUnits.equalsIgnoreCase(POS_UNIT_PIXEL), + yPos, + yUnits.equalsIgnoreCase(POS_UNIT_PIXEL)); + } else { + throw new StepExecutionException("No active window.", //$NON-NLS-1$ + EventFactory.createActionError( + TestErrorEvent.NO_ACTIVE_WINDOW)); + } + } + + /** + * 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 window is expected to exist and be + * visible, otherwise <code>false</code>. + */ +// public void gdCheckExistenceOfWindow(final String title, String operator, +// boolean exists) { +// Verifier.equals(exists, isWindowOpen(title, operator)); +// } + + /** + * Just a server side method, not useable as action. + * + * @param keyCode The key code + */ + public void gdKeyType(int keyCode) { + getRobot().keyType(null, keyCode); + } + + /** + * Just a server side method, not useable as action. + * + * note : this action only works if application got focus, + * because using defaultToolkit does not work. You have to + * use component.getToolKit()s + * @param key to set + * numlock Num Lock 1 + * caplock Caps Lock 2 + * scolllock Scroll 3 + * @param activated + * boolean + */ + public void gdToggle(int key, boolean activated) { + + int event = getEventCode(key); + if (event != 0) { + try { + getRobot().keyToggle(getFocusOwner(), + event, activated); + } catch (UnsupportedOperationException usoe) { + throw new StepExecutionException( + TestErrorEvent.UNSUPPORTED_OPERATION_ERROR, + EventFactory.createActionError( + TestErrorEvent.UNSUPPORTED_OPERATION_ERROR)); + } catch (OsNotSupportedException e) { + throw new StepExecutionException( + TestErrorEvent.UNSUPPORTED_OPERATION_ERROR, + EventFactory.createActionError( + TestErrorEvent.UNSUPPORTED_OPERATION_ERROR)); + } + } + } + + /** + * perform a keystroke + * @param modifierSpec the string representation of the modifiers + * @param keySpec the string representation of the key + */ + public abstract void gdKeyStroke(String modifierSpec, String keySpec); + /** + * + * @return the Focus Owner + */ + protected abstract Object getFocusOwner(); + /** + * + * @param key to set + * numlock Num Lock 1 + * caplock Caps Lock 2 + * scolllock Scroll 3 + * @return the toolkit specific eventcode + */ + protected abstract int getEventCode(int key); + + /** + * + * @return The active application window, or <code>null</code> if no + * application window is currently active. + */ + protected abstract Object getActiveWindow(); + + +} diff --git a/org.eclipse.jubula.rc.swing/src/org/eclipse/jubula/rc/swing/swing/caps/SwingApplicationCAPs.java b/org.eclipse.jubula.rc.swing/src/org/eclipse/jubula/rc/swing/swing/caps/SwingApplicationCAPs.java new file mode 100644 index 000000000..b96294437 --- /dev/null +++ b/org.eclipse.jubula.rc.swing/src/org/eclipse/jubula/rc/swing/swing/caps/SwingApplicationCAPs.java @@ -0,0 +1,577 @@ +/******************************************************************************* + * 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.swing.swing.caps; + +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.Dialog; +import java.awt.Frame; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.AWTEventListener; +import java.awt.event.ComponentEvent; +import java.awt.event.KeyEvent; +import java.awt.event.WindowEvent; +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.caps.AbstractApplicationCAPs; +import org.eclipse.jubula.rc.common.driver.ClickOptions; +import org.eclipse.jubula.rc.common.driver.IRobot; +import org.eclipse.jubula.rc.common.exception.StepExecutionException; +import org.eclipse.jubula.rc.common.implclasses.MatchUtil; +import org.eclipse.jubula.rc.common.implclasses.Verifier; +import org.eclipse.jubula.rc.common.listener.EventLock; +import org.eclipse.jubula.rc.common.logger.AutServerLogger; +import org.eclipse.jubula.rc.common.util.KeyStrokeUtil; +import org.eclipse.jubula.rc.swing.components.SwingComponent; +import org.eclipse.jubula.rc.swing.listener.ComponentHandler; +import org.eclipse.jubula.rc.swing.listener.FocusTracker; +import org.eclipse.jubula.rc.swing.swing.implclasses.EventListener; +import org.eclipse.jubula.rc.swing.swing.implclasses.WindowHelper; +import org.eclipse.jubula.rc.swing.swing.interfaces.IGraphicApplication; +import org.eclipse.jubula.tools.constants.StringConstants; +import org.eclipse.jubula.tools.objects.event.EventFactory; +import org.eclipse.jubula.tools.objects.event.TestErrorEvent; +import org.eclipse.jubula.tools.utils.TimeUtil; +/** + * + * @author BREDEX GmbH + * + */ +public class SwingApplicationCAPs extends AbstractApplicationCAPs implements + IGraphicApplication { + + /** + * This condition is true if the event is an 'window opened' event + * and the event source is a frame/dialog with a certain title. + * It is also true if the event is a 'component shown' event and the + * event source is a frame/dialog with a certain title. + */ + private static class WindowOpenedCondition + implements EventListener.Condition { + /** + * the title + */ + private final String m_title; + + /** the matches operation */ + private final String m_operator; + + /** + * constructor + * + * @param title the title + * @param operator the matches operation + */ + public WindowOpenedCondition(String title, String operator) { + m_title = title; + m_operator = operator; + } + /** + * {@inheritDoc} + */ + public boolean isTrue(AWTEvent event) { + if (event.getID() != WindowEvent.WINDOW_OPENED + && event.getID() != ComponentEvent.COMPONENT_SHOWN) { + return false; + } + if (event.getSource() instanceof Frame) { + Frame frame = (Frame)event.getSource(); + return MatchUtil.getInstance().match( + frame.getTitle(), m_title, m_operator); + } else if (event.getSource() instanceof Dialog) { + Dialog dialog = (Dialog)event.getSource(); + return MatchUtil.getInstance().match( + dialog.getTitle(), m_title, m_operator); + } else { + return false; + } + } + } + + /** + * This condition is true if the event is an 'window activated' event + * and the event source is a frame/dialog with a certain title. + */ + private static class WindowActivatedCondition + implements EventListener.Condition { + /** + * the title + */ + private final String m_title; + + /** the matches operation */ + private final String m_operator; + + /** + * constructor + * + * @param title the title + * @param operator the matches operation + */ + public WindowActivatedCondition(String title, String operator) { + m_title = title; + m_operator = operator; + } + /** + * {@inheritDoc} + */ + public boolean isTrue(AWTEvent event) { + if (event.getID() != WindowEvent.WINDOW_ACTIVATED) { + return false; + } + if (event.getSource() instanceof Frame) { + Frame frame = (Frame)event.getSource(); + return MatchUtil.getInstance().match( + frame.getTitle(), m_title, m_operator); + } else if (event.getSource() instanceof Dialog) { + Dialog dialog = (Dialog)event.getSource(); + return MatchUtil.getInstance().match( + dialog.getTitle(), m_title, m_operator); + } else { + return false; + } + } + } + + /** + * This condition is true if the event is an 'window closed' event + * and the event source is a frame/dialog with a certain title. + * It is also true if the event is a 'component hidden' event and the + * event source is a frame/dialog with a certain title. + */ + private static class WindowClosedCondition + implements EventListener.Condition { + /** + * the title + */ + private final String m_title; + + /** the matches operation */ + private final String m_operator; + + /** + * constructor + * + * @param title the title + * @param operator the matches operation + */ + public WindowClosedCondition(String title, String operator) { + m_title = title; + m_operator = operator; + } + /** + * {@inheritDoc} + */ + public boolean isTrue(AWTEvent event) { + if (event.getID() != WindowEvent.WINDOW_CLOSED + && event.getID() != ComponentEvent.COMPONENT_HIDDEN) { + return false; + } + if (event.getSource() instanceof Frame) { + Frame frame = (Frame)event.getSource(); + return MatchUtil.getInstance().match( + frame.getTitle(), m_title, m_operator); + } else if (event.getSource() instanceof Dialog) { + Dialog dialog = (Dialog)event.getSource(); + return MatchUtil.getInstance().match( + dialog.getTitle(), m_title, m_operator); + } else { + return false; + } + } + } + + /** + * The logging. + */ + private static AutServerLogger log = + new AutServerLogger(SwingApplicationCAPs.class); + + /** + * {@inheritDoc} + */ + public String[] getTextArrayFromComponent() { + return null; + } + + + /** + * Types <code>text</code> into the component. This replaces the shown + * content. + * @param text the text to type in + * @deprecated Removed without substitution: + * Testcases with this action are fragile, because this action assumes the + * availabality of a text component. Any other case breaks the test. + */ + public void gdReplaceText(String text) { + getRobot().click(FocusTracker.getFocusOwner(), null, + ClickOptions.create().setClickCount(3).left()); + if (StringConstants.EMPTY.equals(text)) { + getRobot().keyStroke("DELETE"); //$NON-NLS-1$ + } + gdInputText(text); + } + + /** + * Waits <code>timeMillSec</code> if the application opens a window + * with the given title. + * + * @param title the title + * @param operator the comparing operator + * @param pTimeout the time in ms + * @param delay delay after the window is shown + */ + public void gdWaitForWindow(final String title, String operator, + int pTimeout, int delay) { + + EventListener.Condition cond = + new WindowOpenedCondition(title, operator); + EventLock lock = new EventLock(); + AWTEventListener listener = new EventListener(lock, cond); + Toolkit.getDefaultToolkit().addAWTEventListener(listener, + AWTEvent.WINDOW_EVENT_MASK | AWTEvent.COMPONENT_EVENT_MASK); + + if (isWindowOpen(title, operator)) { + lock.release(); + } + try { + synchronized (lock) { + long timeout = pTimeout; + long done = System.currentTimeMillis() + timeout; + long now; + while (!lock.isReleased() && (timeout > 0)) { + try { + lock.wait(timeout); + now = System.currentTimeMillis(); + timeout = done - now; + } catch (InterruptedException e) { + // ignore + } + } + } + } finally { + Toolkit.getDefaultToolkit().removeAWTEventListener(listener); + } + if (!lock.isReleased() && !isWindowOpen(title, operator)) { + 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 pTimeout the time in ms + * @param delay delay after the window is activated + */ + public void gdWaitForWindowActivation(final String title, String operator, + int pTimeout, int delay) { + + EventListener.Condition cond = new WindowActivatedCondition(title, + operator); + EventLock lock = new EventLock(); + AWTEventListener listener = new EventListener(lock, cond); + Toolkit.getDefaultToolkit().addAWTEventListener(listener, + AWTEvent.WINDOW_EVENT_MASK); + + if (isWindowActive(title, operator)) { + lock.release(); + } + try { + synchronized (lock) { + long timeout = pTimeout; + long done = System.currentTimeMillis() + timeout; + long now; + while (!lock.isReleased() && (timeout > 0)) { + try { + lock.wait(timeout); + now = System.currentTimeMillis(); + timeout = done - now; + } catch (InterruptedException e) { + // ignore + } + + } + } + } finally { + Toolkit.getDefaultToolkit().removeAWTEventListener(listener); + } + if (!lock.isReleased() && !isWindowActive(title, operator)) { + 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 pTimeout the time in ms + * @param delay delay after the window is closed + */ + public void gdWaitForWindowToClose(final String title, String operator, + int pTimeout, int delay) { + + EventListener.Condition cond = + new WindowClosedCondition(title, operator); + EventLock lock = new EventLock(); + AWTEventListener listener = new EventListener(lock, cond); + + Toolkit.getDefaultToolkit().addAWTEventListener(listener, + AWTEvent.WINDOW_EVENT_MASK); + if (!isWindowOpen(title, operator)) { + lock.release(); + } + + try { + synchronized (lock) { + long timeout = pTimeout; + long done = System.currentTimeMillis() + timeout; + long now; + while (!lock.isReleased() && (timeout > 0)) { + try { + lock.wait(timeout); + now = System.currentTimeMillis(); + timeout = done - now; + } catch (InterruptedException e) { + // ignore + } + } + } + } finally { + Toolkit.getDefaultToolkit().removeAWTEventListener(listener); + } + if (!lock.isReleased() && isWindowOpen(title, operator)) { + throw new StepExecutionException("window did not close", //$NON-NLS-1$ + EventFactory.createActionError( + TestErrorEvent.TIMEOUT_EXPIRED)); + } + + TimeUtil.delay(delay); + } + + /** + * 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 window is expected to exist and be + * visible, otherwise <code>false</code>. + */ + public void gdCheckExistenceOfWindow(final String title, String operator, + boolean exists) { + Verifier.equals(exists, isWindowOpen(title, operator)); + } + + /** + * {@inheritDoc} + */ + public Rectangle getActiveWindowBounds() { + Window activeWindow = WindowHelper.getActiveWindow(); + if (activeWindow != null) { + Rectangle activeWindowBounds = + new Rectangle(activeWindow.getBounds()); + activeWindowBounds.setLocation(activeWindow.getLocationOnScreen()); + + return activeWindowBounds; + } + return null; + } + /** + * {@inheritDoc} + */ + protected IRobot getRobot() { + return AUTServer.getInstance().getRobot(); + } + + /** + * perform a keystroke specified according <a + * href=http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/KeyStroke.html#getKeyStroke(java.lang.String)> + * string representation of a keystroke </a>, + * + * @param modifierSpec the string representation of the modifiers + * @param keySpec the string representation of the key + */ + public void gdKeyStroke(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()); + } + String key = keySpec.trim().toUpperCase(); + String mod = KeyStrokeUtil.getModifierString(modifierSpec); + if (mod.length() > 0) { + getRobot().keyStroke(mod.toString() + " " + key); //$NON-NLS-1$ + } else { + int code = getKeyCode(key); + if (code != -1) { + gdKeyType(code); + } else { + getRobot().keyStroke(key); + } + } + } + /** + * {@inheritDoc} + */ + protected Object getFocusOwner() { + return FocusTracker.getFocusOwner(); + } + + /** + * {@inheritDoc} + */ + protected int getEventCode(int key) { + int event = 0; + switch (key) { + case 1 : + event = KeyEvent.VK_NUM_LOCK; + break; + case 2 : + event = KeyEvent.VK_CAPS_LOCK; + break; + case 3 : + event = KeyEvent.VK_SCROLL_LOCK; + break; + default : + break; + } + return event; + } + + /** + * {@inheritDoc} + */ + protected Object getActiveWindow() { + return WindowHelper.getActiveWindow(); + } + + /** + * 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(String title, String operator) { + boolean wasInterrupted; + do { + try { + wasInterrupted = false; + Collection components = ComponentHandler.getAutHierarchy() + .getHierarchyMap().keySet(); + for (Iterator it = components.iterator(); it.hasNext();) { + Component c = ((SwingComponent)it.next()) + .getRealComponent(); + if (c.isShowing()) { + if (c instanceof Frame) { + Frame frame = (Frame)c; + if (MatchUtil.getInstance().match(frame.getTitle(), + title, operator)) { + + return true; + } + } + if (c instanceof Dialog) { + Dialog dialog = (Dialog)c; + if (MatchUtil.getInstance().match(dialog.getTitle(), + title, operator)) { + + return true; + } + } + } + } + } catch (ConcurrentModificationException e) { + log.debug("hierarchy modified while traversing", e); //$NON-NLS-1$ + wasInterrupted = true; + } + } while (wasInterrupted); + return false; + } + + /** + * Returns <code>true</code> if a window with the given title has focus + * + * @param title the title + * @param operator the matches/equals operator + * @return if the window has focus + */ + private boolean isWindowActive(String title, String operator) { + + Window activeWindow = WindowHelper.getActiveWindow(); + if (activeWindow != null) { + String windowTitle = null; + if (activeWindow instanceof Dialog) { + windowTitle = ((Dialog)activeWindow).getTitle(); + } else if (activeWindow instanceof Frame) { + windowTitle = ((Frame)activeWindow).getTitle(); + } + + if (MatchUtil.getInstance().match(windowTitle, title, operator)) { + return true; + } + } + + return false; + } + + /** + * @param keyCodeName + * The name of a key code, e.g. <code>TAB</code> for a + * tabulator key code + * @return The key code or <code>-1</code>, if the key code name doesn't + * exist in the <code>KeyEvent</code> class + * @throws StepExecutionException + * If the key code name cannot be converted to a key code due to + * the reflection call + */ + public int getKeyCode(String keyCodeName) throws StepExecutionException { + int code = -1; + String codeName = "VK_" + keyCodeName; //$NON-NLS-1$ + try { + code = KeyEvent.class.getField(codeName).getInt(KeyEvent.class); + } catch (IllegalArgumentException e) { + throw new StepExecutionException(e.getMessage(), EventFactory + .createActionError()); + } catch (SecurityException e) { + throw new StepExecutionException(e.getMessage(), EventFactory + .createActionError()); + } catch (IllegalAccessException e) { + throw new StepExecutionException(e.getMessage(), EventFactory + .createActionError()); + } catch (NoSuchFieldException e) { + if (log.isInfoEnabled()) { + log.info("The key expression '" + keyCodeName //$NON-NLS-1$ + + "' is not a key code, typed as key stroke instead"); //$NON-NLS-1$ + } + } + return code; + } +} diff --git a/org.eclipse.jubula.rc.swt/src/org/eclipse/jubula/rc/swt/caps/SwtApplicationCAPs.java b/org.eclipse.jubula.rc.swt/src/org/eclipse/jubula/rc/swt/caps/SwtApplicationCAPs.java new file mode 100644 index 000000000..8b70a01ff --- /dev/null +++ b/org.eclipse.jubula.rc.swt/src/org/eclipse/jubula/rc/swt/caps/SwtApplicationCAPs.java @@ -0,0 +1,548 @@ +/******************************************************************************* + * 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.caps; + +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.caps.AbstractApplicationCAPs; +import org.eclipse.jubula.rc.common.driver.ClickOptions; +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.implclasses.MatchUtil; +import org.eclipse.jubula.rc.common.implclasses.Verifier; +import org.eclipse.jubula.rc.common.listener.EventLock; +import org.eclipse.jubula.rc.common.logger.AutServerLogger; +import org.eclipse.jubula.rc.common.util.KeyStrokeUtil; +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.implclasses.EventListener; +import org.eclipse.jubula.rc.swt.implclasses.EventListener.Condition; +import org.eclipse.jubula.rc.swt.interfaces.IGraphicApplication; +import org.eclipse.jubula.rc.swt.listener.ComponentHandler; +import org.eclipse.jubula.rc.swt.listener.FocusTracker; +import org.eclipse.jubula.rc.swt.utils.SwtPointUtil; +import org.eclipse.jubula.tools.constants.StringConstants; +import org.eclipse.jubula.tools.objects.event.EventFactory; +import org.eclipse.jubula.tools.objects.event.TestErrorEvent; +import org.eclipse.jubula.tools.utils.EnvironmentUtils; +import org.eclipse.jubula.tools.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 SwtApplicationCAPs extends AbstractApplicationCAPs implements + IGraphicApplication { + + /** The logging. */ + private static AutServerLogger log = + new AutServerLogger(SwtApplicationCAPs.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; + } + return MatchUtil.getInstance().match(window.getText(), + 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; + } + + /** + * Types <code>text</code> into the component. This replaces the shown + * content. + * @param text the text to type in + * @deprecated Removed without substitution: + * Testcases with this action are fragile, because this action assumes the + * availabality of a text component. Any other case breaks the test. + */ + public void gdReplaceText(String text) { + // The number of clicks differs from the Swing implementation + // because a double-click selects all of the text + getRobot().click(FocusTracker.getFocusOwner(), null, + ClickOptions.create().setClickCount(2).left()); + if (StringConstants.EMPTY.equals(text)) { + getRobot().keyStroke("DELETE"); //$NON-NLS-1$ + } + gdInputText(text); + } + + /** + * 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 gdWaitForWindow(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() { //$NON-NLS-1$ + public Object 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() { //$NON-NLS-1$ + public Object 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 gdWaitForWindowActivation(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() { //$NON-NLS-1$ + public Object 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() { //$NON-NLS-1$ + public Object 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 gdWaitForWindowToClose(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() { //$NON-NLS-1$ + public Object 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() { //$NON-NLS-1$ + public Object 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()).getRealComponent(); + if (comp instanceof Shell + && !comp.isDisposed() + && ((Shell)comp).isVisible()) { + + Shell frame = (Shell)comp; + if (MatchUtil.getInstance().match( + 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>. + */ + public void gdCheckExistenceOfWindow(final String title, + final String operator, final boolean exists) { + IEventThreadQueuer queuer = new EventThreadQueuerSwtImpl(); + Boolean windowExists = (Boolean)queuer.invokeAndWait( + "isWindowOpen", new IRunnable() { //$NON-NLS-1$ + public Object run() throws StepExecutionException { + return new Boolean(isWindowOpen(title, operator)); + } + }); + Verifier.equals(exists, windowExists.booleanValue()); + } + + /** + * {@inheritDoc} + */ + public Rectangle getActiveWindowBounds() { + org.eclipse.swt.graphics.Rectangle activeWindowSize = + (org.eclipse.swt.graphics.Rectangle)getRobotFactory() + .getEventThreadQueuer().invokeAndWait( + this.getClass().getName() + ".getActiveWindowBounds", //$NON-NLS-1$ + new IRunnable() { + public Object run() { // SYNCH THREAD START + 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 gdKeyStroke(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) { + gdNativeKeyStroke(modifierSpec, keySpec); + } else { + // at this the key stroke specification is not fully fullfilled as the + // key stroke spec base key is not definitly 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() { + Shell activeWindow = (Shell)getRobotFactory().getEventThreadQueuer() + .invokeAndWait(this.getClass().getName() + ".getActiveWindow", //$NON-NLS-1$ + + new IRunnable() { + public Object run() { // SYNCH THREAD START + Display d = + ((SwtAUTServer)AUTServer.getInstance()).getAutDisplay(); + return d.getActiveShell(); + + } + } + + ); + + return activeWindow; + } + + /** + * 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 = activeWindow.getText(); + + return MatchUtil.getInstance().match(windowTitle, title, operator); + } + +} diff --git a/org.eclipse.jubula.toolkit.provider.swing/resources/xml/ComponentConfiguration.xml b/org.eclipse.jubula.toolkit.provider.swing/resources/xml/ComponentConfiguration.xml index 5379fddc7..adb632332 100644 --- a/org.eclipse.jubula.toolkit.provider.swing/resources/xml/ComponentConfiguration.xml +++ b/org.eclipse.jubula.toolkit.provider.swing/resources/xml/ComponentConfiguration.xml @@ -21,7 +21,7 @@ <typeFactory>org.eclipse.jubula.rc.common.implclasses.DefaultComponentFactory</typeFactory>
</defaultMapping>
<realizes>guidancer.concrete.GraphicApplication</realizes>
- <testerClass>org.eclipse.jubula.rc.swing.swing.implclasses.SwingApplicationImplClass</testerClass>
+ <testerClass>org.eclipse.jubula.rc.swing.swing.caps.SwingApplicationCAPs</testerClass>
<componentClass name="com.bredexsw.guidancer.autserver.swing.implclasses.GraphicApplication" />
</toolkitComponent>
diff --git a/org.eclipse.jubula.toolkit.provider.swt/resources/xml/ComponentConfiguration.xml b/org.eclipse.jubula.toolkit.provider.swt/resources/xml/ComponentConfiguration.xml index 1e2594078..7353acf7e 100644 --- a/org.eclipse.jubula.toolkit.provider.swt/resources/xml/ComponentConfiguration.xml +++ b/org.eclipse.jubula.toolkit.provider.swt/resources/xml/ComponentConfiguration.xml @@ -194,7 +194,7 @@ <typeFactory>org.eclipse.jubula.rc.common.implclasses.DefaultComponentFactory</typeFactory>
</defaultMapping>
<realizes>guidancer.concrete.GraphicApplication</realizes>
- <testerClass>org.eclipse.jubula.rc.swt.implclasses.SwtApplicationImplClass</testerClass>
+ <testerClass>org.eclipse.jubula.rc.swt.caps.SwtApplicationCAPs</testerClass>
<componentClass name="com.bredexsw.guidancer.autswtserver.implclasses.GraphicApplication" />
</toolkitComponent>
|
