package org.jboss.tools.common.reddeer.ext;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchSite;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.jboss.reddeer.common.exception.WaitTimeoutExpiredException;
import org.jboss.reddeer.common.logging.Logger;
import org.jboss.reddeer.common.wait.TimePeriod;
import org.jboss.reddeer.common.wait.WaitUntil;
import org.jboss.reddeer.core.exception.CoreLayerException;
import org.jboss.reddeer.core.handler.WidgetHandler;
import org.jboss.reddeer.core.lookup.ShellLookup;
import org.jboss.reddeer.core.lookup.WorkbenchPartLookup;
import org.jboss.reddeer.core.matcher.AndMatcher;
import org.jboss.reddeer.core.matcher.ClassMatcher;
import org.jboss.reddeer.core.matcher.MatcherBuilder;
import org.jboss.reddeer.core.reference.ReferencedComposite;
import org.jboss.reddeer.core.resolver.WidgetResolver;
import org.jboss.reddeer.core.util.DiagnosticTool;
import org.jboss.reddeer.core.util.Display;
import org.jboss.reddeer.core.util.ResultRunnable;
/**
* Widget lookup provides methods for looking up eclipse widgets.
*
* @author Jiri Peterka
* @author Jaroslav Jankovic
*/
public class WidgetLookupExt {
private static WidgetLookupExt instance = null;
private static final Logger logger = Logger.getLogger(WidgetLookupExt.class);
private WidgetLookupExt() {
}
/**
* Gets instance of WidgetLookupExt.
*
* @return WidgetLookupExt instance
*/
public static WidgetLookupExt getInstance() {
if (instance == null)
instance = new WidgetLookupExt();
return instance;
}
/**
* Method looks for active widget located in specified referenced composite, laying on specified index and matching
* specified matchers.
*
* @param <T>
* the generic type
* @param refComposite
* reference composite to search for widgets
* @param clazz
* class type of widget
* @param index
* index of widget within referenced composite
* @param matchers
* matchers to match widget
* @return widget located withing specified referenced composite, laying on specified index and matching specified
* matchers
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public <T extends Widget> T activeWidget(ReferencedComposite refComposite, Class<T> clazz, int index,
Matcher... matchers) {
logger.debug("Looking up active widget with class type " + clazz.getName() + ", index " + index + " and "
+ createMatcherDebugMsg(matchers));
ClassMatcher cm = new ClassMatcher(clazz);
Matcher[] allMatchers = MatcherBuilder.getInstance().addMatcher(matchers, cm);
AndMatcherExt am = new AndMatcherExt(allMatchers);
Control parentControl = getParentControl(refComposite);
WidgetIsFoundExt found = new WidgetIsFoundExt(parentControl, index, am.getMatchers());
try {
new WaitUntil(found, TimePeriod.SHORT);
} catch (WaitTimeoutExpiredException ex) {
String exceptionText = "No matching widget found with " + am.toString();
exceptionText += "\n" + new DiagnosticTool().getDiagnosticInformation(parentControl);
logger.error("Active widget with class type " + clazz.getName() + " and index " + index + " was not found");
throw new CoreLayerException(exceptionText, ex);
}
logger.debug("Active widget with class type " + clazz.getName() + " and index " + index + " was found");
return (T) found.getWidget();
}
/**
* Method looks for active widgets located in specified referenced composite and matching specified matchers.
*
* @param <T>
* the generic type
* @param refComposite
* reference composite to search for widgets
* @param clazz
* class type of widgets
* @param matchers
* matchers to match widget
* @return widgets located in specified referenced composite and matching specified matchers
*/
public <T extends Widget> List<T> activeWidgets(ReferencedComposite refComposite, Class<T> clazz,
Matcher<?>... matchers) {
logger.debug("Looking up active widgets with class type " + clazz.getName() + " and "
+ createMatcherDebugMsg(matchers));
ClassMatcher cm = new ClassMatcher(clazz);
Matcher<?>[] allMatchers = MatcherBuilder.getInstance().addMatcher(matchers, cm);
AndMatcher am = new AndMatcher(allMatchers);
List<T> foundWidgets = activeWidgets(refComposite.getControl(), am);
logger.debug("Found " + foundWidgets.size() + " widgets");
return foundWidgets;
}
private Control getParentControl(ReferencedComposite refComposite) {
if (refComposite == null) {
return findParent();
}
return refComposite.getControl();
}
/**
* Finds parent control of active widget .
*
* @return parent control of active widget or throws an exception if null
*/
public Control findParent() {
logger.debug("No parent specified, finding one");
Control parent = getActiveWidgetParentControl();
logger.debug("Parent found successfully");
if (parent == null) {
logger.error("Unable to determine active parent");
throw new CoreLayerException("Unable to determine active parent");
}
return parent;
}
/**
* Finds active widget or reference composite matching given matcher.
*
* @param <T>
* the generic type
* @param refComposite
* given reference composite
* @param matcher
* given matcher
* @return active widget
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public <T extends Widget> List<T> activeWidgets(Control refComposite, Matcher matcher) {
logger.trace("Looking up widgets with specified parent and matchers");
List<T> widgets = findControls(refComposite, matcher, true);
logger.trace(widgets.size() + " widget(s) found");
return widgets;
}
/**
* Finds out whether extra shell (shell different than workbench shell) is active.
*
* @return true if extra shell is active, false otherwise
*/
public boolean isExtraShellActive() {
IWorkbenchPartReference activeWorkbenchReference = WorkbenchPartLookup.getInstance()
.findActiveWorkbenchPartReference();
Shell activeWorkbenchParentShell = getShellForActiveWorkbench(activeWorkbenchReference);
Shell activeShell = ShellLookup.getInstance().getActiveShell();
if (activeWorkbenchParentShell == null || activeWorkbenchParentShell != activeShell) {
return true;
}
return false;
}
/**
* Gets active parent control. Method finds either active workbench referenced control active shell.
*
* @return active workbench control or active shell
*/
public Control getActiveWidgetParentControl() {
Control control = null;
IWorkbenchPartReference activeWorkbenchReference = WorkbenchPartLookup.getInstance()
.findActiveWorkbenchPartReference();
Shell activeWorkbenchParentShell = getShellForActiveWorkbench(activeWorkbenchReference);
Shell activeShell = ShellLookup.getInstance().getActiveShell();
if ((activeWorkbenchParentShell == null || !activeWorkbenchParentShell.equals(activeShell))
&& activeShell != null) {
logger.trace("Setting active shell with title \"" + WidgetHandler.getInstance().getText(activeShell)
+ "\" as the parent");
control = activeShell;
} else {
if (activeWorkbenchReference != null) {
logger.trace("Setting workbench part with title \"" + getTitle(activeWorkbenchReference)
+ "\"as the parent");
control = WorkbenchPartLookup.getInstance().getWorkbenchControl(activeWorkbenchReference);
}
}
return control;
}
/**
* Gets the shell for active workbench.
*
* @param workbenchReference
* the workbench reference
* @return the shell for active workbench
*/
protected Shell getShellForActiveWorkbench(IWorkbenchPartReference workbenchReference) {
if (workbenchReference == null) {
return null;
}
IWorkbenchPart wPart = workbenchReference.getPart(true);
if (wPart == null) {
return null;
}
IWorkbenchSite wSite = wPart.getSite();
if (wSite == null) {
return null;
}
return wSite.getShell();
}
/**
* Get widget with given index from list of widgets .
*
* @param <T>
* the generic type
* @param widgets
* list of widgets
* @param index
* widget index
* @return widget with given index or null if out of range
*/
public <T extends Widget> T getProperWidget(List<T> widgets, int index) {
T widget = null;
if (widgets.size() > index) {
logger.trace("Selecting widget with the specified index (" + index + ")");
widget = widgets.get(index);
} else {
logger.trace("The specified index is bigger than the size of found widgets (" + index + " > "
+ widgets.size() + ")");
}
return widget;
}
/**
* Finds list of controls matching specified matcher for active parent widget.
*
* @param <T>
* the generic type
* @param matcher
* matcher to match parent controls
* @param recursive
* true for recursive lookup of control widgets
* @return list of parent controls for active parent or single parent control
*/
public <T extends Widget> List<T> findActiveParentControls(final Matcher<T> matcher, final boolean recursive) {
List<T> findControls = findControls(getActiveWidgetParentControl(), matcher, recursive);
return findControls;
}
/**
* Finds list of controls matching specified matchers for parent widget.
*
* @param parentWidget
* parent widget to search for controls
* @param matcher
* matcher to match controls
* @param recursive
* true for recursive lookup
* @return list of control widgets matching specified matchers of single parent control
*/
private <T extends Widget> List<T> findControls(final Widget parentWidget, final Matcher<T> matcher,
final boolean recursive) {
List<T> ret = Display.syncExec(new ResultRunnable<List<T>>() {
@Override
public List<T> run() {
List<T> findControlsUI = findControlsUI(parentWidget, matcher, recursive);
return findControlsUI;
}
});
return ret;
}
/**
* Gets control with focus.
*
* @return control with focus
*/
public Control getFocusControl() {
Control c = Display.syncExec(new ResultRunnable<Control>() {
@Override
public Control run() {
Control focusControl = Display.getDisplay().getFocusControl();
return focusControl;
}
});
return c;
}
/**
* Gets list of children control widgets located within specified parent widget matching specified matcher.
*
* @param parentWidget
* parent widget
* @param matcher
* matcher to match widgets
* @param recursive
* true for recursive search, false otherwise
* @return children control widget matching specified matcher
*/
@SuppressWarnings("unchecked")
private <T extends Widget> List<T> findControlsUI(final Widget parentWidget, final Matcher<T> matcher,
final boolean recursive) {
if ((parentWidget == null) || parentWidget.isDisposed())
return new ArrayList<T>();
if (!visible(parentWidget)) {
return new ArrayList<T>();
}
LinkedHashSet<T> controls = new LinkedHashSet<T>();
if (matcher.matches(parentWidget) && !controls.contains(parentWidget))
try {
controls.add((T) parentWidget);
} catch (ClassCastException exception) {
throw new IllegalArgumentException("The specified matcher should only match against is declared type.",
exception);
}
if (recursive) {
List<Widget> children = WidgetResolver.getInstance().getChildren(parentWidget);
controls.addAll(findControlsUI(children, matcher, recursive));
}
return new ArrayList<T>(controls);
}
/**
* Gets list of children control widgets matching specified matcher from specified list of widgets. Method can be
* used recursively to get all children in descendants.
*
* Note: Must be used in UI Thread
*
* @param widgets
* list of widgets to get children from
* @param matcher
* matcher to match widgets
* @param recursive
* true for recursive search, false otherwise
* @return list of children widgets of widgets from specified list
*/
private <T extends Widget> List<T> findControlsUI(final List<Widget> widgets, final Matcher<T> matcher,
final boolean recursive) {
LinkedHashSet<T> list = new LinkedHashSet<T>();
for (Widget w : widgets) {
list.addAll(findControlsUI(w, matcher, recursive));
}
return new ArrayList<T>(list);
}
/**
* Finds out whether widget is visible or not.
*
* @param w
* widget to resolve
* @return true if widget is visible, false otherwise
*/
private boolean visible(Widget w) {
return !((w instanceof Control) && !((Control) w).getVisible());
}
private String getTitle(final IWorkbenchPartReference part) {
return Display.syncExec(new ResultRunnable<String>() {
@Override
public String run() {
return part.getTitle();
}
});
}
private String createMatcherDebugMsg(Matcher<?>[] matchers) {
StringBuilder sb = new StringBuilder();
if (matchers.length == 0) {
sb.append("no matchers specified");
} else {
sb.append("following matchers specified (");
}
for (int ind = 0; ind < matchers.length; ind++) {
sb.append(matchers[ind].getClass());
if (ind < matchers.length - 1) {
sb.append(", ");
} else {
sb.append(")");
}
}
return sb.toString();
}
/**
* Find all parent widgets.
*
* @return the list
*/
public List<Control> findAllParentWidgets() {
List<Control> allWidgets = findControls(findParent(), new BaseMatcher<Control>() {
@Override
public boolean matches(Object obj) {
return true;
}
@Override
public void describeTo(Description desc) {
}
}, true);
return allWidgets;
}
}