package abbot.tester.swt;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleIcon;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Decorations;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;
import abbot.BugReport;
import abbot.Log;
import abbot.WaitTimedOutError;
import abbot.finder.swt.SWTHierarchy;
import abbot.finder.swt.TestHierarchy;
import abbot.script.Condition;
import com.windowtester.runtime.swt.internal.abbot.SWTWorkarounds;
import com.windowtester.runtime.swt.internal.operation.SWTKeyOperation;
import com.windowtester.runtime.swt.internal.operation.SWTPushEventOperation;
/**
* This is the base class for tester objects. WidgetTester primarily contains
* three types of methods:
* <ol>
* <li>action* methods, for executing a particular action on a widget</li>
* <li>get* methods, for obtaining info about the current state of a widget from any thread</li>
* <li>assert* methods, for making assertions about a widget's state from any thread</li>
* </ol>
*
* @author Kevin Dale
* @version $Id: WidgetTester.java,v 1.5 2007-11-13 23:57:14 pq Exp $
*/
/* TODO:
*
* 1) Where should swt extensions go (as is, in abbot.tester.extensions)?
* [for now, we'll leave it in abbot.tester.extensions]
* 2) Are we to put waitForIdle() calls here, or not?
* [for now, we'll include them only where they were included before]
* 3) What params should keyPress,keyRelease,etc take?
* [for now, we'll NOT use strings as with ComponentTester]
*/
public class WidgetTester extends Robot {
public static final String copyright = "Licensed Materials -- Property of IBM\n"+
"(c) Copyright International Business Machines Corporation, 2003\nUS Government "+
"Users Restricted Rights - Use, duplication or disclosure restricted by GSA "+
"ADP Schedule Contract with IBM Corp.";
//!pq: flag to indicate whether to emit verbose trace info to the console
private static final boolean TRACE = false;
private TestHierarchy hierarchy = null; /* for use in deriveTag across recursive calls */
/**
* These getter methods return a particular property of the given widget.
* @see the corresponding member function in class Widget
*/
/* Begin getters */
/**
* Proxy for {@link Widget#getData()}.
* <p/>
* @param w the widget to obtain the data from.
* @return the data stored with the widget.
*/
public Object getData(final Widget w){
Object result = Robot.syncExec(w.getDisplay(), new RunnableWithResult() {
public Object runWithResult() {
return w.getData();
}
});
return result;
}
/**
* Proxy for {@link Widget#getData(java.lang.String)}.
* <p/>
* @param w the widget to obtain the data from.
* @param key the key under which the data is stored.
* @return the data associated with the key given.
*/
public Object getData(final Widget w, final String key){
Object result = Robot.syncExec(w.getDisplay(), new RunnableWithResult() {
public Object runWithResult() {
return w.getData(key);
}
});
return result;
}
/**
* Proxy for {@link Widget#getDisplay()}.
* <p/>
* @param w the widget to obtain the display from.
* @return the display associated with the widget given.
*/
public Display getDisplay(final Widget w){
Display result = (Display)Robot.syncExec(w.getDisplay(), new RunnableWithResult() {
public Object runWithResult() {
return w.getDisplay();
}
});
return result;
}
/**
* Proxy for {@link Widget#getStyle()}.
* <p/>
* @param w the widget to obtain the style for.
* @return the style.
*/
public int getStyle(final Widget w){
Integer result = (Integer) Robot.syncExec(w.getDisplay(), new RunnableWithResult() {
public Object runWithResult() {
return new Integer(w.getStyle());
}
});
return result.intValue();
}
/**
* Proxy for {@link Widget#isDisposed()}.
* <p/>
* @param w the widget to ask.
* @return true if the widget has been disposed.
*/
public boolean isDisposed(final Widget w){
Boolean result = (Boolean) Robot.syncExec(w.getDisplay(), new RunnableWithResult() {
public Object runWithResult() {
return Boolean.valueOf(w.isDisposed());
}
});
return result.booleanValue();
}
/**
* Proxy for {@link Widget#isListening(int eventType)}.
* <p/>
* @param w the widget to ask.
* @return true if the widget is listening for the given event.
*/
public boolean isListening(final Widget w, final int eventType){
Boolean result = (Boolean) Robot.syncExec(w.getDisplay(), new RunnableWithResult() {
public Object runWithResult() {
return Boolean.valueOf(SWTWorkarounds.isListening(w, eventType));
}
});
return result.booleanValue();
}
/**
* Proxy for {@link Widget#toString()}.
* <p/>
* @param w the widget to obtain the toString from.
* @return
*/
public static String toString(final Widget w){
//@todo: this is static b/c super class Robot overrides toString to be static
String result = (String)Robot.syncExec(w.getDisplay(), new RunnableWithResult() {
public Object runWithResult() {
return w.toString();
}
});
return result;
}
/* End getters */
/* Begin setters/adders */
/**
* Proxy for {@link Widget#addDisposeListener(org.eclipse.swt.events.DisposeListener)}.
* <p/>
* @param w the widget to add the listener to.
* @param disposeListener the listener to add.
*/
public void addDisposeListener(final Widget w, final DisposeListener disposeListener) {
Robot.syncExec(w.getDisplay(), null, new Runnable() {
public void run() {
w.addDisposeListener(disposeListener);
}
});
}
/**
* Proxy for {@link Widget#addListener(int, org.eclipse.swt.widgets.Listener)}.
* <p/>
* @param w the Widget to add the listener to.
* @param eventType the eventType for which a listener to add.
* @param listener the listener to add.
*/
public void addListener(final Widget w, final int eventType, final Listener listener) {
Robot.syncExec(w.getDisplay(), null, new Runnable() {
public void run() {
w.addListener(eventType, listener);
}
});
}
/**
* Proxy for {@link Widget#notifyListeners(int, org.eclipse.swt.widgets.Event)}.
* <p/>
* @param w the widgets which listeners should be notified.
* @param eventType the eventType to notify.
* @param event the event to issue.
*/
public void notifyListeners(final Widget w, final int eventType, final Event event) {
Robot.syncExec(w.getDisplay(), null, new Runnable() {
public void run() {
w.notifyListeners(eventType, event);
}
});
}
/**
* Proxy for {@link Widget#removeDisposeListener(org.eclipse.swt.events.DisposeListener)}.
* <p/>
* @param w the Widget from which to remove the DisposeListener.
* @param disposeListener the listener to remove.
*/
public void removeDisposeListener(final Widget w, final DisposeListener disposeListener) {
Robot.syncExec(w.getDisplay(), null, new Runnable() {
public void run() {
w.removeDisposeListener(disposeListener);
}
});
}
/**
* Proxy for {@link Widget#removeListener(int, org.eclipse.swt.widgets.Listener)}.
* <p/>
* @param w the Widget from which the listener to remove.
* @param eventType the eventType being removed.
* @param listener the listener to remove.
*/
public void removeListener(final Widget w, final int eventType, final Listener listener ) {
Robot.syncExec(w.getDisplay(), null, new Runnable() {
public void run() {
w.removeListener(eventType, listener);
}
});
}
/**
* Proxy for {@link Widget#setData(java.lang.Object)}.
* <p/>
* @param w the Widget whichs data should be set.
* @param data the data to set.
*/
public void setData(final Widget w, final Object data) {
Robot.syncExec(w.getDisplay(), null, new Runnable() {
public void run() {
w.setData(data);
}
});
}
/**
* Proxy for {@link Widget#setData(java.lang.String, java.lang.Object)}.
* <p/>
* @param w the widget whichs data to set.
* @param key the key under shich the data should be stored.
* @param data the data to store.
*/
public void setData(final Widget w, final String key, final Object data) {
Robot.syncExec(w.getDisplay(), null, new Runnable() {
public void run() {
w.setData(key, data);
}
});
}
/* End setters/adders */
/*
* TODO: Copied from old DefaultWidgetFinder. Is this the right place for this?
*/
public String getWidgetText(final Widget widget){
String res = null;
Method getText=null;
WidgetTester tester=null;
Class[] paramTypes = {widget.getClass()};
Object[] params = {widget};
boolean foundMethod = false;
Class widgetClass = widget.getClass();
while(!foundMethod && (tester==null||tester.getClass()!=WidgetTester.class)){
try{
//getText = widget.getClass().getMethod("getText",null);
tester = WidgetTester.getTester(widgetClass);
getText =tester.getClass().getMethod("getText",paramTypes);
foundMethod = true;
}
catch(NoSuchMethodException nsme){
widgetClass = widgetClass.getSuperclass();
paramTypes[0]=widgetClass;
}
}
if(getText==null)
return null;
try{
res = (String)getText.invoke(tester,params);
}
catch(Exception e){
e.printStackTrace();
return null;
}
return res;
}
/**
* Get the location of the widget in global screen coordinates
**/
public Point getGlobalLocation(Widget w){
return getGlobalLocation(w,true);
}
/**
* Get the location of the widget in global screen coordinates,
* optionally ignoring the 'trimmings'.
*/
public Point getGlobalLocation(final Widget w, final boolean ignoreBorder){
Point result = (Point) Robot.syncExec(w.getDisplay(), new RunnableWithResult() {
public Object runWithResult() {
return WidgetLocator.getLocation(w,ignoreBorder);
}
});
return result;
}
/**
* Get the bounding rectangle for the given Widget in global
* screen coordinates.
*/
public Rectangle getGlobalBounds(Widget w){
return getGlobalBounds(w,true);
}
/**
* Get the bounding rectangle for the given Widget in global
* screen coordinates, optionally ignoring the 'trimmings'.
*/
public Rectangle getGlobalBounds(final Widget w, final boolean ignoreBorder){
Rectangle result = (Rectangle) Robot.syncExec(w.getDisplay(), new RunnableWithResult() {
public Object runWithResult() {
return WidgetLocator.getBounds(w,ignoreBorder);
}
});
return result;
}
/** Maps class names to their corresponding Tester object. */
private static HashMap testers = new HashMap();
/** Establish the given WidgetTester as the one to use for the given
* class. This may be used to override the default tester for a given
* core class. Note that this will only work with widgets loaded by ////
* the framework class loader, not those loaded by the class loader for ////
* the code under test. ////
*/
public static void setTester(Class forClass, WidgetTester tester) {
testers.put(forClass.getName(), tester);
}
/** Return the appropriate Tester for the given object. */
public static WidgetTester getTester(Widget widget) {
return widget != null ? getTester(widget.getClass())
: getTester(Widget.class);
}
/**
* Factory method.
*/
public static WidgetTester getWidgetTester() {
return (WidgetTester)(getTester(Widget.class));
}
/** Find the corresponding Tester object for the given widget class,
chaining up the inheritance tree if no specific tester is found for
that class.<p>
The abbot tester package is searched first, followed by the tester
extensions package.
*/
public static WidgetTester getTester(Class widgetClass) {
Log.debug("Looking up tester for " + widgetClass);
if (!Widget.class.isAssignableFrom(widgetClass)) {
String msg = "Class " + widgetClass.getName()
+ " is not derived from org.eclipse.swt.widgets.Widget";
throw new IllegalArgumentException(msg);
}
WidgetTester tester = (WidgetTester)
testers.get(widgetClass.getName());
if (tester == null) {
String cname = simpleClassName(widgetClass) + "Tester";
String pkg = WidgetTester.class.getPackage().getName();
tester = findTester(pkg + "." + cname, widgetClass);
if (tester == null) {
tester = findTester(pkg + ".extensions." + cname,
widgetClass);
if (tester == null) {
tester = getTester(widgetClass.getSuperclass());
}
}
if (tester != null && !tester.isExtension()) {
// Only cache it if it's part of the standard framework,
// but cache it for every level that we looked up, so we
// don't repeat the effort.
testers.put(widgetClass.getName(), tester);
}
}
return tester;
}
//\/(extensions to swt stuff may go elsewhere)
/** Return whether this tester is an extension. */
public final boolean isExtension() {
return getClass().getName().startsWith("abbot.tester.extensions");
}
/** Look up the given class, using special class loading rules to maintain
framework consistency. */
private static Class resolveClass(String testerName, Class widgetClass)
throws ClassNotFoundException {
// Extension testers must be loaded in the context of the code under
// test.
Class cls;
if (testerName.startsWith("abbot.tester.extensions")) {
cls = Class.forName(testerName, true,
widgetClass.getClassLoader());
}
else {
cls = Class.forName(testerName);
}
Log.debug("Loaded class " + testerName + " with "
+ cls.getClassLoader());
return cls;
}
///\
/** Look up the given class with a specific class loader. */
private static WidgetTester findTester(String testerName,
Class widgetClass) {
WidgetTester tester = null;
Class testerClass = null;
try {
testerClass = resolveClass(testerName, widgetClass);
tester = (WidgetTester)testerClass.newInstance();
}
catch(InstantiationException ie) {
Log.warn(ie);
}
catch(IllegalAccessException iae) {
Log.warn(iae);
}
catch(ClassNotFoundException cnf) {
//Log.debug("Class " + testerName + " not found");
}
catch(ClassCastException cce) {
throw new BugReport("Class loader conflict: environment "
+ WidgetTester.class.getClassLoader()
+ " vs. " + testerClass.getClassLoader());
}
return tester;
}
// /**
// * @deprecated
// */
// protected abbot.swt.WidgetFinder getFinder() {
// return abbot.swt.DefaultWidgetFinder.getFinder();
// }
/** Derive a tag from the given accessible context if possible, or return
* null.
*/
protected String deriveAccessibleTag(AccessibleContext context) {
String tag = null;
if (context != null) {
if (context.getAccessibleName() != null) {
tag = context.getAccessibleName();
}
if ((tag == null || "".equals(tag))
&& context.getAccessibleIcon() != null
&& context.getAccessibleIcon().length > 0) {
AccessibleIcon[] icons = context.getAccessibleIcon();
tag = icons[0].getAccessibleIconDescription();
if (tag != null) {
tag = tag.substring(tag.lastIndexOf("/") + 1);
tag = tag.substring(tag.lastIndexOf("\\") + 1);
}
}
}
return tag;
}
private static final String[] tagMethods = {
"getText",
"getData"
};
private static final Class[][] tagParamTypes = {
null,
{String.class}
};
private static final Object[][] tagArgs = {
null,
{"name"}
};
/** Return a reasonable identifier for the given widget. */
public static String getTag(Widget widget) {
return getTester(widget.getClass()).deriveTag(widget);
}
/** Provide a String that is fairly distinct for the given widget. For
* a generic widget, attempt to look up some common patterns such as a
* title or label. Derived classes should absolutely override this method
* if such a String exists.<p>
* Don't use widget names as tags.<p>
*/
public String deriveTag(Widget widget) {
Method m = null;
String tag = null;
// Try a few default methods
for (int i=0;i < tagMethods.length;i++) {
// Don't use getText on text components
if ((widget instanceof Text)
&& "getText".equals(tagMethods[i])) {
continue;
}
try {
m = widget.getClass().getMethod(tagMethods[i], tagParamTypes[i]);
String tmp = (String)m.invoke(widget, tagArgs[i]);
// Don't ever use empty strings for tags
if (tmp != null && !"".equals(tmp)) {
tag = tmp;
break;
}
}
catch(Exception e) {
// System.err.println("tagMethods["+i+"] = "+m);
// System.err.println(tagMethods[i]);
// System.err.println(tagParamTypes[i]);
// System.err.println(tagArgs[i]);
}
}
// In the absence of any other tag, try to derive one from something
// recognizable on one of its ancestors.
if (tag == null || "".equals(tag)) {
/* To fix this it will be necessary to go back and find the
* place in hierarchy where getparent was implemented and
* perhaps finish the implementation.
* */
// Widget parent = DefaultWidgetFinder.getFinder().getWidgetParent(widget);
/* need to keep hierarchy across recursive calls */
//TestHierarchy hierarchy = new TestHierarchy(widget.getDisplay());
if (hierarchy==null) {
hierarchy = new TestHierarchy(widget.getDisplay());
}
Widget parent = hierarchy.getParent(widget);
if (parent != null) {
String ptag = getTag(parent);
if (ptag != null && !"".equals(tag)) {
// Don't use the tag if it's simply the window title; that
// doesn't provide any extra information.
if (!ptag.endsWith(" Root Pane")) {
StringBuffer buf = new StringBuffer(ptag);
int under = ptag.indexOf(" under ");
if (under != -1)
buf = buf.delete(0, under + 7);
buf.insert(0, " under ");
buf.insert(0, simpleClassName(widget.getClass()));
tag = buf.toString();
}
}
}
}
return tag;
}
/**
* Wait for an idle AWT event queue. Will return when there are no more
* events on the event queue.
*/
public void actionWaitForIdle() {
waitForIdle();
}
/**
* Wait for an idle AWT event queue. Will return when there are no more
* events on the event queue for the given display.
*/
public void actionWaitForIdle(Display d){
waitForIdle(d);
}
/** Delay the given number of ms. */
public void actionDelay(int ms) {
delay(ms);
}
public void actionSelectMenuItemByText(Menu menu, String text) {
selectMenuItemByText(menu, text);
}
// FIXME do actionSelectMenuItemByName in addition to by component
// FIXME do actionSelectMenuItemByPath (for dynamics) which follows labels
/** Select the given menu item. */
public void actionSelectMenuItem(MenuItem item) {
Log.debug("Attempting to select menu item " + toString(item));
selectMenuItem(item);
}
/** Open the item's popup menu at the given coordinates of its parent control, and
* select the given item.
*/
public void actionSelectPopupMenuItem(MenuItem item, int x, int y){
selectPopupMenuItem(item,x,y);
}
/** Click on the center of the widget. */
public void actionClick(Widget widget) {
click(widget);
// waitForIdle(widget.getDisplay());
// What if the widget is a cancel button?
// It will be promptly disposed.
if ((widget != null) && (!widget.isDisposed())) {
waitForIdle(widget.getDisplay());
}
}
/** Click on the widget at the given location. */
public void actionClick(Widget widget, int x, int y) {
click(widget, x, y, SWT.BUTTON1);
// waitForIdle(widget.getDisplay());
// What if the widget is a cancel button?
// It will be promptly disposed.
if ((widget != null) && (!widget.isDisposed())) {
waitForIdle(widget.getDisplay());
}
}
/** Click on the widget at the given location. The buttons string
* should be the org.eclipse.swt.SWT field name for the mask.
*/
public void actionClick(Widget widget, int x, int y, String buttons) {
click(widget,x,y,getModifiers(buttons));
// waitForIdle(widget.getDisplay());
// What if the widget is a cancel button?
// It will be promptly disposed.
if ((widget != null) && (!widget.isDisposed())) {
waitForIdle(widget.getDisplay());
}
}
/**
* Click on the widget at the given location. The buttons string
* should be the org.eclipse.swt.SWT field name for the mask. This
* variation provides for multiple clicks.
*/
public void actionClick(Widget comp, int x, int y,
String buttons, int count) {
click(comp, x, y, getModifiers(buttons), count);
waitForIdle(comp.getDisplay());
}
/*TODO implement Robot.getKeyCode(String) and change these keyMethods to take
* Strings as parameters
*/
/**
* Press the keys contained in the given accelerator
*/
public void actionKeyPress(int accelerator,Display d){
keyPress(accelerator);
waitForIdle(d);
}
/**
* Release the keys contained in the given accelerator
*/
public void actionKeyRelease(int accelerator,Display d) {
keyRelease(accelerator);
waitForIdle(d);
}
/**
* Press, and release, the keys contained in the given accelerator
*/
public void actionKey(int accelerator,Display d){
key(accelerator);
waitForIdle(d);
}
/**
* Type the given character. Note that this sends the key to whatever
* component currently has the focus.
*/
public void actionKeyChar(char c,Display d){
key((int)c);
waitForIdle(d);
}
/**
* Type the given string.
*/
public void actionKeyString(String string,Display d){
keyString(string);
waitForIdle(d);
}
/** Set the focus on to the given component. */
/* TODO MAY NEED TO CHECK THAT THE CONTROL DOES INDEED HAVE FOCUS */
public void actionFocus(Widget widget) {
TestHierarchy hierarchy = new TestHierarchy(Display.getDefault());
while(!(widget instanceof Control))
widget = hierarchy.getParent(widget);
focus((Control)widget);
waitForIdle(widget.getDisplay());
}
/** Perform a drag action. Derived classes should provide more specific
* identifiers for what is being dragged, e.g. actionDragTableCell or
* actionDragListElement.
*/
public void actionDrag(Widget source, int x, int y){
drag(source,x,y,SWT.BUTTON1);
waitForIdle(source.getDisplay());
}
/** Perform a drag action. Derived classes should provide more specific
* identifiers for what is being dragged, e.g. actionDragTableCell or
* actionDragListElement. The modifiers represents the set of active
* modifiers when the drop is made.
*/
public void actionDrag(Widget source, int x, int y, int modifiers){
drag(source,x,y,modifiers);
waitForIdle(source.getDisplay());
}
/** Perform a basic drop action (implicitly causing a preceding mouse
* drag).
*/
public void actionDrop(Widget target, int x, int y){
drop(target,x,y,SWT.BUTTON1);
waitForIdle(target.getDisplay());
}
/** Perform a basic drop action (implicitly causing a preceding mouse
* drag). The modifiers represents the set of active modifiers when the
* drop is made.
*/
public void actionDrop(Widget target, int x, int y, int modifiers){
drop(target,x,y,modifiers);
waitForIdle(target.getDisplay());
}
/** Return whether the widget's contents matches the given image. */
public boolean assertImage(Widget widget, Image image,
boolean ignoreBorder) {
Image widgetImage = capture(widget, ignoreBorder);
SWTImageComparator ic = new SWTImageComparator();
boolean result = (ic.compare(image, widgetImage) == 0);
widgetImage.dispose();
return result;
}
/** Wait for the given condition, throwing an ActionFailedException if it
* times out.
*/
protected void waitAction(String desc, Condition cond)
throws ActionFailedException {
try { wait(cond); }
catch(WaitTimedOutError wto) {
throw new ActionFailedException(desc);
}
}
/** Returns whether Decorations corresponding to the given String is
* showing. The string may be a plain String or regular expression and
* may match either the Decoration's title or name
*/
public synchronized boolean assertDecorationsShowing(String title) {
return assertDecorationsShowing(title, true);
}
// FIXME provide more options for identifying the window (name, title,
// class, nth window)
/* Support added to allow a search of either all decorations or just those that
* are top-level decorations (root windows) */
public synchronized boolean assertDecorationsShowing(final String title, boolean topOnly) {
boolT = false;
//System.out.println ("ADS: " + title + " / " + topOnly);
if (topOnly) {
/* only search top-level decorations */
Display[] displays = DecorationsTracker.getDisplays();
//ArrayList decorationsList = new ArrayList();
for (int i=0;i<displays.length;i++) {
Collection decorationsList = DecorationsTracker.getTracker(displays[i]).getRootDecorations();
final Iterator iter = decorationsList.iterator();
Robot.syncExec(displays[i],this,new Runnable(){
public void run(){
while (iter.hasNext()) {
Decorations d = (Decorations)iter.next();
if (!d.isDisposed() && d.getText().equals(title)) {
boolT = d.isVisible();
}
}
}
});
}
} else {
// TODO: clean up and document this code
// This code basically traverses the widget hierarchy looking for Decorations
// We also have to make sure we run the methods that get text and other things
// from the correct thread
/* search all decorations */
Display[] displays = DecorationsTracker.getDisplays();
ArrayList decorationsList = new ArrayList();
for (int i=0;i<displays.length;i++) {
//System.out.println("Display " + i + ": " + displays[i]);
SWTHierarchy hierarchy = new TestHierarchy(displays[i]);
final Iterator rootIter = hierarchy.getRoots().iterator();
while (rootIter.hasNext()) {
decorationsList.addAll(hierarchy.getWidgets((Widget)rootIter.next()));
}
decorationsList.addAll(hierarchy.getRoots());
//hierarchy.dbPrintWidgets();
final Iterator decorationsIter = decorationsList.iterator();
ArrayList shellList = new ArrayList();
while (decorationsIter.hasNext()){
Object d = decorationsIter.next();
//System.out.println ("Widget ("+d.hashCode()+") " + i + ": " + d + " belongs to: " + ((Widget)d).getDisplay());
/* remove non-Decorations and those that don't belong to this display */
if (
( (d instanceof Decorations)
)
&& ((Widget)d).getDisplay().equals(displays[i])) {
shellList.add(d);
}
}
final Iterator iter = shellList.iterator();
Runnable runThread = new Runnable() {
public void run(){
while (iter.hasNext()) {
Decorations d = (Decorations)iter.next();
if (!d.isDisposed() && d.getText().equals(title)) {
boolT = d.isVisible();
}
}
}
};
if (displays[i].getThread().equals(Thread.currentThread())) {
/* run in this thread */
while (iter.hasNext()) {
Decorations d = (Decorations)iter.next();
if (!d.isDisposed() && d.getText().equals(title)) {
boolT = d.isVisible();
}
}
} else {
Robot.syncExec(displays[i],this,runThread);
}
}
}
//System.out.println ("ADS returning: " + boolT);
return boolT;
}
/** Convenience wait for a window to be displayed. The given string may
* be a plain String or regular expression and may match either the window
* title or its Widget name. This method is provided as a convenience
* for hand-coded tests, since scripts will use a wait step instead. The
* property abbot.robot.component_delay affects the default timeout
* (default is 30s).
*/
public static void waitForFrameShowing(final String title) {
wait(new Condition() {
public boolean test() {
return getTester(Widget.class).assertDecorationsShowing(title);
}
public String toString() { return title + " to show"; }
}, componentDelay);
}
/** Convenience wait for a shell to be displayed. This method is like
* waitForFrameShowing, with the exception that this method searches all
* shells in all displays; the former only searches for top-level shells
*/
public static void waitForShellShowing(final String title) {
waitForShellShowing(title, componentDelay);
}
/**
* Convenience wait for a shell to be displayed. This method is like
* waitForFrameShowing, with the exception that this method searches all
* shells in all displays; the former only searches for top-level shells
*
* @param delay in millis
*/
public static void waitForShellShowing(final String title, final int delay) {
wait(new Condition() {
public boolean test() {
return getTester(Widget.class).assertDecorationsShowing(title, false);
}
public String toString() { return title + " to show"; }
}, delay);
}
// /** Return whether the Widget represented by the given
// WidgetReference is available.
// */
// public boolean assertWidgetShowing(WidgetReference ref) {
// try {
// findWidget(ref);
// return true;
// }
// catch(Exception e) {
// return false;
// }
//
// }
// /**
// * Return the Widget represented by the given
// WidgetReference.
// * @throws MultipleWidgetsFoundException
// * @throws WidgetNotFoundException
// */
// public static Widget findWidget(WidgetReference ref) throws WidgetNotFoundException, MultipleWidgetsFoundException {
// /* should this be nonstatic? */
// /* try to find the widget using finder / matcher */
// Matcher [] widgetToFind = {
// ref.getText() != null && !ref.getText().equals("") ? new TextMatcher (ref.getText()) : null,
// ref.getName() != null && !ref.getName().equals("") ? new NameMatcher (ref.getName()) : null,
// /* new ClassMatcher (ref.getClass() ) */ null
// /* TODO: match on other stuff such as tag, etc.
// * No tagmatcher currently exists -- necessary?
// * */
// };
// WidgetFinder finder = BasicFinder.getDefault();
// Widget result = finder.find(new CompositeMatcher(widgetToFind));
// //getFinder().findWidget(ref);
// return result;
// }
// /**
// * Wait for the Widget represented by the given WidgetReference to
// * become available. The timeout is affected by
// * abbot.robot.component_delay, which defaults to 30s.
// * @deprecated Use the new matcher API in abbot.finder.swt.
// */
// public static void waitForWidgetShowing(final WidgetReference ref) {
// wait(new Condition() {
// public boolean test() {
// return getTester(Widget.class).assertWidgetShowing(ref);
// }
// public String toString() { return ref + " to show"; }
// }, componentDelay);
// }
private Method[] cachedMethods = null;
/** Look up methods with the given prefix. */
private Method[] getMethods(String prefix, Class returnType,
boolean onWidget) {
if (cachedMethods == null) {
cachedMethods = getClass().getMethods();
}
ArrayList methods = new ArrayList();
HashSet names = new HashSet();
Method[] mlist = cachedMethods;
for (int i=0;i < mlist.length;i++) {
String name = mlist[i].getName();
if (!names.contains(name)) {
Class[] params = mlist[i].getParameterTypes();
if (name.startsWith(prefix)
&& (returnType == null
|| returnType.equals(mlist[i].getReturnType()))
&& ((params.length == 0 && !onWidget)
|| (params.length > 0
&& (Widget.class.isAssignableFrom(params[0])
== onWidget)))) {
methods.add(mlist[i]);
names.add(name);
}
}
}
return (Method[])methods.toArray(new Method[methods.size()]);
}
private Method[] cachedActions = null;
/** Return a list of all actions defined by this class that don't depend
* on a widget argument.
*/
public Method[] getActions() {
if (cachedActions == null) {
cachedActions = getMethods("action", void.class, false);
}
return cachedActions;
}
private Method[] cachedComponentActions = null;
/** Return a list of all actions defined by this class that require
* a widget argument.
*/
public Method[] getWidgetActions() {
if (cachedComponentActions == null) {
cachedComponentActions = getMethods("action", void.class, true);
}
return cachedComponentActions;
}
private Method[] cachedPropertyMethods = null;
/** Return an array of all property check methods defined by this class.
* The first argument <b>must</b> be a Widget.
*/
public Method[] getPropertyMethods() {
if (cachedPropertyMethods == null) {
ArrayList all = new ArrayList();
all.addAll(Arrays.asList(getMethods("is", boolean.class, true)));
all.addAll(Arrays.asList(getMethods("get", null, true)));
// Remove getXXX or isXXX methods which aren't property checks
Class[] args = new Class[] { Widget.class };
try {
all.remove(getClass().getMethod("getTag", args));
all.remove(getClass().getMethod("getTester", args));
all.remove(getClass().getMethod("isOnPopup", args));
}
catch(NoSuchMethodException e) {
}
cachedPropertyMethods =
(Method[])all.toArray(new Method[all.size()]);
}
return cachedPropertyMethods;
}
private Method[] cachedAssertMethods = null;
/** Return a list of all assertions defined by this class that don't
* depend on a widget argument.
*/
public Method[] getAssertMethods() {
if (cachedAssertMethods == null) {
cachedAssertMethods = getMethods("assert", boolean.class, false);
}
return cachedAssertMethods;
}
private Method[] cachedComponentAssertMethods = null;
/** Return a list of all assertions defined by this class that require a
* widget argument.
*/
public Method[] getComponentAssertMethods() {
if (cachedComponentAssertMethods == null) {
cachedComponentAssertMethods =
getMethods("assert", boolean.class, true);
}
return cachedComponentAssertMethods;
}
/** Quick and dirty strip raw text from html, for getting the basic text
from html-formatted labels and buttons. Behavior is undefined for
badly formatted html.
*/
public static String stripHTML(String str) {
if (str != null
&& (str.startsWith("<html>")
|| str.startsWith("<HTML>"))) {
while (str.startsWith("<")) {
int right = str.indexOf(">");
if (right == -1)
break;
str = str.substring(right + 1);
}
while (str.endsWith(">")) {
int right = str.lastIndexOf("<");
if (right == -1)
break;
str = str.substring(0, right);
}
}
return str;
}
/** Return the Widget class that corresponds to this WidgetTester
class.
*/
public Class getTestedClass(Class cls) {
while (getTester(cls.getSuperclass()) == this) {
cls = cls.getSuperclass();
}
return cls;
}
// /**
// * Clicks on the control given asynchronously and wait for the shell with the
// * title given to open.
// * <p/>
// * The method is intended to be used with blocking dialogs.
// * @param widget the widget to click on.
// * @param shellTitle the title of the shell to wait for or <code>null</code> if none.
// * @return the shell which has been opened by this action, if findable by the title given.
// */
// /*
// * TODO The click and keyboard actions should be expanded to an asynchronous call, too.
// */
// public Shell actionClickAsync(final Widget widget, final String shellTitle) {
// Runnable clickAsync = new Runnable() {
// public void run() {
// WidgetTester.this.actionClick(widget);
// }
// };
// widget.getDisplay().asyncExec(clickAsync);
// if (shellTitle != null) {
// WidgetTester.waitForShellShowing(shellTitle);
// Shell openedShell = null;
// try {
// openedShell = (Shell) BasicFinder.getDefault().find(new TextMatcher(shellTitle));
// }
// catch (Exception ex) {
// ex.printStackTrace();
// }
// return openedShell;
// }
// return null;
// }
////////////////////////////////////////////////////////////////////////////
//
// Alternative event posting actions
//
////////////////////////////////////////////////////////////////////////////
protected void keyClick(final int keyCode) {
new SWTKeyOperation().keyCode(keyCode).execute();
// keyUp(keyCode);
// keyDown(keyCode);
}
//
// protected void keyUp(final int keyCode) {
// trace("post key down " + keyCode);
// Event event = new Event();
// event.type = SWT.KeyDown;
// event.keyCode = keyCode;
// new SWTPushEventOperation(event).execute();
// }
//
// protected void keyDown(final int keyCode) {
// trace("post key up " + keyCode);
// Event event = new Event();
// event.type = SWT.KeyUp;
// event.keyCode = keyCode;
// new SWTPushEventOperation(event).execute();
// }
//!pq: an alternative way to post mouse press events, sidestepping the robot
protected void mousePress2(int accelerator) {
accelerator &= BUTTON_MASK;
Event event = new Event();
event.type = SWT.MouseDown;
if((accelerator&SWT.BUTTON1)==SWT.BUTTON1)
event.button = 1;
if((accelerator&SWT.BUTTON2)==SWT.BUTTON2)
event.button = 2;
if((accelerator&SWT.BUTTON3)==SWT.BUTTON3)
event.button = 3;
new SWTPushEventOperation(event).execute();
}
//!pq: an alternative way to post mouse press events, sidestepping the robot
protected void mouseRelease2(int accelerator) {
accelerator &= BUTTON_MASK;
Event event = new Event();
event.type = SWT.MouseUp;
if((accelerator&SWT.BUTTON1)==SWT.BUTTON1)
event.button = 1;
if((accelerator&SWT.BUTTON2)==SWT.BUTTON2)
event.button = 2;
if((accelerator&SWT.BUTTON3)==SWT.BUTTON3)
event.button = 3;
new SWTPushEventOperation(event).execute();
}
//!pq: an alternative way to move the mouse, sidestepping the robot
protected void mouseMove2(int x, int y) {
Event event = new Event();
event.type = SWT.MouseMove;
event.x = x;
event.y = y;
new SWTPushEventOperation(event).execute();
}
protected void trace(String msg) {
if (TRACE)
System.out.println(msg);
}
}