/*******************************************************************************
* Copyright (c) 2008 Ketan Padegaonkar and others.
* 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:
* Ketan Padegaonkar - initial API and implementation
* Jan Koehnlein - [bug 416994] filter disposed shells
*******************************************************************************/
package org.eclipse.swtbot.swt.finder.finders;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import org.apache.log4j.Logger;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.swtbot.swt.finder.resolvers.DefaultChildrenResolver;
import org.eclipse.swtbot.swt.finder.resolvers.DefaultParentResolver;
import org.eclipse.swtbot.swt.finder.resolvers.IChildrenResolver;
import org.eclipse.swtbot.swt.finder.resolvers.IParentResolver;
import org.eclipse.swtbot.swt.finder.results.ArrayResult;
import org.eclipse.swtbot.swt.finder.results.ListResult;
import org.eclipse.swtbot.swt.finder.results.WidgetResult;
import org.eclipse.swtbot.swt.finder.utils.MessageFormat;
import org.eclipse.swtbot.swt.finder.utils.SWTUtils;
import org.eclipse.swtbot.swt.finder.utils.TreePath;
import org.hamcrest.Matcher;
/**
* Finds controls matching a particular matcher.
*
* @see UIThreadRunnable
* @author Ketan Padegaonkar <KetanPadegaonkar [at] gmail [dot] com>
* @version $Id$
*/
public class ControlFinder {
/**
* The logging instance for this class.
*/
private static final Logger log = Logger.getLogger(ControlFinder.class);
/** The childrenResolver */
protected final IChildrenResolver childrenResolver;
/** The display */
protected Display display;
/** The parentResolver */
protected final IParentResolver parentResolver;
/**
* Set to true if the control finder should find invisible controls. Invisible controls are ones hidden from the
* display (isVisible() = false)
*
* @since 1.0
*/
public boolean shouldFindInVisibleControls = false;
/**
* Creates a Control finder using {@link DefaultChildrenResolver} and {@link DefaultParentResolver}.
*/
public ControlFinder() {
this(new DefaultChildrenResolver(), new DefaultParentResolver());
}
/**
* Creates a control finder using the given resolvers.
*
* @param childrenResolver the resolver used to resolve children of a control.
* @param parentResolver the resolver used to resolve parent of a control.
*/
public ControlFinder(IChildrenResolver childrenResolver, IParentResolver parentResolver) {
display = SWTUtils.display();
this.childrenResolver = childrenResolver;
this.parentResolver = parentResolver;
}
/**
* Finds the controls in the active shell matching the given matcher.
* <p>
* Note: This method is thread safe.
* </p>
*
* @param matcher the matcher used to find controls in the active shell.
* @return all controls in the active shell that the matcher matches.
* @see Display#getActiveShell()
*/
public <T extends Widget> List<T> findControls(Matcher<T> matcher) {
return findControls(activeShell(), matcher, true);
}
/**
* Finds the controls matching one of the widgets using the given matcher. This will also go recursively though the
* {@code widgets} provided.
*
* @param widgets the list of widgets.
* @param matcher the matcher used to match the widgets.
* @param recursive if the match should be recursive.
* @return all visible widgets in the children that the matcher matches. If recursive is <code>true</code> then find
* the widgets within each of the widget.
*/
public <T extends Widget> List<T> findControls(final List<Widget> widgets, final Matcher<T> matcher, final boolean recursive) {
return findControlsInternal(widgets, matcher, recursive);
}
/**
* Returns true if the widget is a control and it is visible.
* <p>
* This method is not thread safe and must be invoked from the UI thread.
* </p>
* <p>
* TODO visibility of tab items.
* </p>
*
* @param w the widget
* @return <code>true</code> if the control is visible, <code>false</code> otherwise.
* @see Control#getVisible()
* @since 1.0
*/
protected boolean visible(Widget w) {
if (shouldFindInVisibleControls)
return true;
return !((w instanceof Control) && !((Control) w).getVisible());
}
/**
* Finds the controls starting with the given parent widget and uses the given matcher. If recursive is set, it will
* attempt to find the controls recursively in each child widget if they exist.
* <p>
* This method is thread safe.
* </p>
*
* @param parentWidget the parent widget in which controls should be found.
* @param matcher the matcher used to match the widgets.
* @param recursive if the match should be recursive.
* @return all visible widgets in the parentWidget that the matcher matches. If recursive is <code>true</code> then
* find the widget within each of the parentWidget.
*/
public <T extends Widget> List<T> findControls(final Widget parentWidget, final Matcher<T> matcher, final boolean recursive) {
return UIThreadRunnable.syncExec(display, new ListResult<T>() {
public List<T> run() {
return findControlsInternal(parentWidget, matcher, recursive);
}
});
}
/**
* This finds controls using the list of widgets and the matcher. If recursive is set, it will attempt to find the
* controls recursively in each child widget if they exist.
* <p>
* This method is not thread safe and must be invoked from the UI thread.
* </p>
*
* @see #findControls(List, Matcher, boolean)
*/
private <T extends Widget> List<T> findControlsInternal(final List<Widget> widgets, final Matcher<T> matcher, final boolean recursive) {
LinkedHashSet<T> list = new LinkedHashSet<T>();
for (Widget w : widgets) {
list.addAll(findControlsInternal(w, matcher, recursive));
}
return new ArrayList<T>(list);
}
/**
* Find controls starting from the parent widget using the given matcher. If recursive is set, it will attempt to
* find the controls recursively in each child widget if they exist.
* <p>
* This method is not thread safe and must be invoked from the UI thread.
* </p>
*
* @see #findControlsInternal(Widget, Matcher, boolean)
* @throws IllegalArgumentException if the matcher matches an object that is the wrong declared type. For example, a Matcher<Table> that would match a Tree
*/
@SuppressWarnings("unchecked")
private <T extends Widget> List<T> findControlsInternal(final Widget parentWidget, final Matcher<T> matcher, final boolean recursive) {
if ((parentWidget == null) || parentWidget.isDisposed())
return new ArrayList<T>();
if (!visible(parentWidget)) {
if (!isComposite(parentWidget))
log.trace(MessageFormat.format("{0} is not visible, skipping.", parentWidget)); //$NON-NLS-1$
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 = getChildrenResolver().getChildren(parentWidget);
controls.addAll(findControlsInternal(children, matcher, recursive));
}
return new ArrayList<T>(controls);
}
private boolean isComposite(Widget parentWidget) {
return parentWidget.getClass().equals(Composite.class);
}
/**
* Finds the shell matching the given text (shell.getText()).
*
* @param text The text on the Shell
* @return A Shell containing the specified text
*/
public List<Shell> findShells(final String text) {
return UIThreadRunnable.syncExec(new ListResult<Shell>() {
public List<Shell> run() {
ArrayList<Shell> list = new ArrayList<Shell>();
Shell[] shells = getShells();
for (Shell shell : shells) {
if (shell.getText().equals(text))
list.add(shell);
}
return list;
}
});
}
/**
* Gets the registered children resolver. If the resolver had never been set a default resolver will be created.
*
* @return the childrenResolver
*/
public IChildrenResolver getChildrenResolver() {
return childrenResolver;
}
/**
* Gets the registered parent resolver. If the resolver was not registered then a default instance will be returned.
*
* @return the parentResolver
*/
public IParentResolver getParentResolver() {
return parentResolver;
}
/**
* Gets the path to the widget. The path is the list of all parent containers of the widget.
*
* @param w the widget.
* @return the path to the widget w.
*/
public TreePath getPath(Widget w) {
return new TreePath(getParents(w).toArray());
}
/**
* Gets the shells registered with the display.
*
* @return the shells
*/
public Shell[] getShells() {
return UIThreadRunnable.syncExec(display, new ArrayResult<Shell>() {
public Shell[] run() {
Shell[] shells = display.getShells();
List<Shell> undisposedShells = new ArrayList<Shell>();
for(Shell shell: shells) {
if(!shell.isDisposed())
undisposedShells.add(shell);
}
return undisposedShells.toArray(new Shell[undisposedShells.size()]);
}
});
}
/**
* Return the active shell.
*
* @return the active shell.
* @see Display#getActiveShell()
*/
public Shell activeShell() {
Shell activeShell = UIThreadRunnable.syncExec(display, new WidgetResult<Shell>() {
public Shell run() {
return display.getActiveShell();
}
});
if (activeShell != null)
return activeShell;
return UIThreadRunnable.syncExec(display, new WidgetResult<Shell>() {
public Shell run() {
Shell[] shells = getShells();
for (Shell shell : shells)
if (shell.isFocusControl())
return shell;
return null;
}
});
}
private List<Widget> getParents(final Widget w) {
return UIThreadRunnable.syncExec(display, new ListResult<Widget>() {
public List<Widget> run() {
Widget parent = w;
List<Widget> parents = new LinkedList<Widget>();
while (parent != null) {
parents.add(parent);
parent = getParentResolver().getParent(parent);
}
Collections.reverse(parents);
return parents;
}
});
}
}