/*******************************************************************************
* Copyright (c) 2012 Google, Inc.
* 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:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package com.windowtester.runtime.swt.locator.eclipse;
import java.io.Serializable;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import com.windowtester.runtime.IClickDescription;
import com.windowtester.runtime.IUIContext;
import com.windowtester.runtime.WT;
import com.windowtester.runtime.WaitTimedOutException;
import com.windowtester.runtime.WidgetSearchException;
import com.windowtester.runtime.condition.ICondition;
import com.windowtester.runtime.condition.IConditionHandler;
import com.windowtester.runtime.condition.IsVisible;
import com.windowtester.runtime.locator.IWidgetLocator;
import com.windowtester.runtime.locator.IWidgetReference;
import com.windowtester.runtime.swt.condition.SWTIdleCondition;
import com.windowtester.runtime.swt.internal.condition.LocatorClosingHandler;
import com.windowtester.runtime.swt.internal.condition.eclipse.EditorCondition;
import com.windowtester.runtime.swt.internal.condition.eclipse.EditorCondition.Active;
import com.windowtester.runtime.swt.internal.condition.eclipse.EditorCondition.Dirty;
import com.windowtester.runtime.swt.internal.finder.eclipse.editors.EditorFinder;
import com.windowtester.runtime.swt.internal.finder.legacy.InternalMatcherBuilder;
import com.windowtester.runtime.swt.internal.locator.ICloseableLocator;
import com.windowtester.runtime.swt.internal.matchers.eclipse.EditorComponentMatcher;
import com.windowtester.runtime.swt.internal.widgets.ISWTWidgetMatcher;
import com.windowtester.runtime.swt.locator.CTabItemLocator;
import com.windowtester.runtime.swt.locator.SWTWidgetLocator;
import com.windowtester.runtime.util.StringComparator;
/**
* Locates eclipse workbench <code>Editor</code> parts.
* <p>
* Editors are identified by their part name as it shown on the associated
* tab (as defined here: {@link IWorkbenchPartReference#getPartName()}.
*
* <pre>
* new SWTWidgetLocator(StyledText.class,
* new EditorLocator("Smoke.java"));
* </pre>
* <p>
* "Smart matching" is used by default to handle the common case where the part name may or may not
* be prefixed with an asterix ('*') to indicate dirty state. The above locator matches parts named "Smoke.java"
* as well as its dirty counterpart "*Smoke.java".
* <p>
* These new locators may also be used to click the close "X" of an editor, as in
*
* <pre>
* ui.click(new EditorLocator("Smoke.java", WT.CLOSE));
* </pre>
*/
public class EditorLocator extends SWTWidgetLocator implements IEditorLocator, IsVisible {
private static class Closer implements ICloseableLocator, Serializable {
private static final long serialVersionUID = 3502312171322974878L;
private final transient EditorLocator editorLocator;
public Closer(EditorLocator editorLocator) {
this.editorLocator = editorLocator;
}
/* (non-Javadoc)
* @see com.windowtester.runtime.swt.internal.locator.ICloseableLocator#doClose(com.windowtester.runtime.IUIContext, com.windowtester.runtime.locator.IWidgetReference, com.windowtester.runtime.IClickDescription)
*/
public void doClose(IUIContext ui) throws WidgetSearchException {
editorLocator.doClose(ui);
}
}
//TODO: consider a match that takes an IPath...
// --> this will probably give cause for a pluggable matcher:
// EditorComponentMatcher.byName()
// EditorComponentMatcher.byPath()
private static final long serialVersionUID = 8106851292164382419L;
/** The name of the target editor part */
private final String partName;
/**
* A bit flag indicating how the locator should behave.<br>
* Possible values include: <br>
* {@link WT#NO_SMART_MATCH} - turn off smart matching as described in
* {@link #EditorLocator(String)}<br>
* {@link WT#CLOSE} - locate the "X" in the editor locator tab used to close the
* editor
*/
private final int variation;
/**
* Create an instance that locates the given editor by matching the name of
* the part, as it shown on the associated tab (as defined here: {@link IWorkbenchPartReference#getPartName()}.
* <p>
* By default, "smart matching" of the name is used to handle the common case where the part name may or may not
* be prefixed with an asterix ('*') to indicate dirty state. This constructor is equivalent to
* <pre>
* EditorLocator("(\\*)?" + partName);
* </pre>
* If this is not the desired behavior, smart matching can be turned off using the
* two argument constructor {@link #EditorLocator(String, int)}.
* <p>
* Note also that wild cards are supported in the part name. In the case where the smart
* asterix handling might conflict with a chosen wildcard, prefer the two argument constructor
* with smart matching disabled.
*
* @param partName the name of the editor to locate
* (can be a regular expression as described in the {@link StringComparator} utility)
*/
public EditorLocator(String partName) {
this(partName, WT.NONE);
}
/**
* Create an instance that locates the given editor by matching the name of the part,
* as it shown on the associated tab (as defined here:
* {@link IWorkbenchPartReference#getPartName()}.
*
* @param partName the name of the editor to locate (can be a regular expression as
* described in the {@link StringComparator} utility)
* @param variation a bit flag indicating how the locator should behave.<br>
* Possible values include: <br>
* {@link WT#NO_SMART_MATCH} - turn off smart matching as described in
* {@link #EditorLocator(String)}<br>
* {@link WT#CLOSE} - locate the "X" in the editor locator tab used to
* close the editor
*/
public EditorLocator(String partName, int variation) {
super(Control.class);
this.partName = partName;
this.variation = variation;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "EditorLocator ["+ getPartName() +"]";
}
/**
* Get this editor locator's part name
*/
public String getPartName() {
return partName;
}
/* (non-Javadoc)
* @see com.windowtester.runtime.swt.locator.SWTWidgetLocator#buildMatcher()
*/
protected ISWTWidgetMatcher buildMatcher() {
String matchString = buildMatchString(getPartName());
if (isClose())
return InternalMatcherBuilder.build2(new CTabItemLocator(matchString));
return new EditorComponentMatcher(matchString);
}
private String buildMatchString(String partName) {
return buildMatchString(partName, isSmartMatch());
}
private static String buildMatchString(String name, boolean smartMatch) {
if (!smartMatch)
return name;
return "(\\*)?" + name;
}
/*
* Name is just the part name...
*/
public String getNameOrLabel() {
return getPartName();
}
public IWidgetLocator click(IUIContext ui, IWidgetReference widget, IClickDescription click)
throws WidgetSearchException
{
//NOTE [02.05.2009: close is now done exclusively programmatically
if (isClose()) {
doClose(ui);
return widget; //NOTE: this reference will be invalidated by the close
}
return super.click(ui, widget, click);
}
/**
* Closes the given editor.
* NOTE: the actual close is done asynchronously since dirty editors may
* force a prompt blocking the UI thread.
*/
private void doClose(IUIContext ui) throws WaitTimedOutException {
ui.wait(new SWTIdleCondition());
if (ui.findAll(this).length > 0) {
final IEditorReference[] references = EditorFinder.findEditors(buildMatchString(getPartName()));
if (references != null && references.length == 1) {
final Display display = PlatformUI.getWorkbench().getDisplay();
final IEditorPart[] editor = new IEditorPart[1];
final IWorkbenchPage[] page = new IWorkbenchPage[1];
display.syncExec(new Runnable() {
public void run() {
editor[0] = references[0].getEditor(true);
if (editor != null /*&& !editor.isDirty() */) {
IWorkbench workbench = PlatformUI.getWorkbench();
if (workbench != null) {
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
if (window != null) {
page[0] = window.getActivePage();
}
}
}
}
});
if (page[0] == null || editor[0] == null)
return;
//notice that this is done as an async in case the editor is dirty and forces a prompt
display.asyncExec(new Runnable() {
public void run() {
page[0].closeEditor(editor[0], true);
}
});
}
}
}
protected final boolean isSmartMatch() {
return (variation & WT.NO_SMART_MATCH) == 0;
}
protected final boolean isClose() {
return (variation & WT.CLOSE) != 0;
}
/* (non-Javadoc)
* @see com.windowtester.runtime.swt.locator.SWTWidgetLocator#isVisible(com.windowtester.runtime.IUIContext)
*/
public boolean isVisible(IUIContext ui) throws WidgetSearchException {
return EditorCondition.isVisible(this).test();
}
//////////////////////////////////////////////////////////////////////////////
//
// Condition factories
//
//////////////////////////////////////////////////////////////////////////////
public ICondition isActive() {
return EditorCondition.isActive(this);
}
public ICondition isActive(boolean expected) {
Active active = EditorCondition.isActive(this);
if (!expected)
return active.not();
return active;
}
public ICondition isDirty() {
return EditorCondition.isDirty(this);
}
public ICondition isDirty(boolean expected) {
Dirty dirty = EditorCondition.isDirty(this);
if (!expected)
return dirty.not();
return dirty;
}
/* (non-Javadoc)
* @see com.windowtester.runtime.WidgetLocator#getAdapter(java.lang.Class)
*/
@SuppressWarnings("unchecked")
public Object getAdapter(Class adapter) {
if (adapter == ICloseableLocator.class)
return new Closer(this);
return super.getAdapter(adapter);
}
/**
* Create a condition handler that ensures that this Editor is closed.
*
* @since 5.0.0
*/
public IConditionHandler isClosed() {
return new LocatorClosingHandler(this);
}
}