package com.rcpcompany.test.utils.ui;
/*******************************************************************************
* Copyright (c) 2006-2013 The RCP Company 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:
* The RCP Company - initial API and implementation
*******************************************************************************/
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IExecutionListener;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.commands.NotHandledException;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.bindings.keys.ParseException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
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.Table;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.services.IServiceLocator;
import com.rcpcompany.test.utils.BaseTestUtils;
import com.rcpcompany.utils.basic.ui.TSSWTUtils;
import com.rcpcompany.utils.logging.LogUtils;
/**
* Base class for all tests.
* <p>
* Provides a number of convenience methods...
*
* @author Tonny Madsen, The RCP Company
* @since 1.1
*/
public class UITestUtils {
private UITestUtils() {
}
/**
* Resets the complete test environment.
*/
public static void resetUI() {
final Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
Event e = new Event();
e.type = SWT.KeyUp;
e.keyCode = SWT.SHIFT;
e.stateMask = 0;
e.widget = shell;
shell.getDisplay().post(e);
e = new Event();
e.type = SWT.KeyUp;
e.keyCode = SWT.CTRL;
e.stateMask = 0;
e.widget = shell;
shell.getDisplay().post(e);
e = new Event();
e.type = SWT.KeyUp;
e.keyCode = SWT.ALT;
e.stateMask = 0;
e.widget = shell;
shell.getDisplay().post(e);
}
private static int testViewSeq = 0;
/**
* Opens and returns a new test view.
*
* @param creatingObject
* the object of the caller - used to name the new view
*
* @return the new view
*/
public static TestView createTestView(Object creatingObject) {
TestView view = null;
try {
final IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
view = (TestView) page.showView("com.rcpcompany.test.utils.ui.TestView", "" + (testViewSeq++),
IWorkbenchPage.VIEW_ACTIVATE);
assertNotNull(view);
view.setPartName("Test View: " + creatingObject.getClass().getSimpleName());
} catch (final Exception ex) {
fail(ex.getMessage());
}
view.getSite().getPage().activate(view);
return view;
}
/**
* Opens and returns the view with the specified ID
*
* @param viewID
* the view id
*
* @return the new view
*/
public static IViewPart showView(String viewID) {
IViewPart view = null;
try {
final IWorkbench workbench = PlatformUI.getWorkbench();
assertNotNull("no workbench?", workbench);
final IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
assertNotNull("no active window?", window);
final IWorkbenchPage page = window.getActivePage();
assertNotNull("no active page?", page);
view = page.showView(viewID, null, IWorkbenchPage.VIEW_ACTIVATE);
assertNotNull(view);
} catch (final Exception ex) {
LogUtils.error(null, "viewID=" + viewID, ex);
fail("" + ex);
}
view.getSite().getPage().activate(view);
return view;
}
/**
* Sleeps for the specified number of msec.
*
* @param msec
* the length in msec
*/
public static void sleep(int msec) {
if (msec <= 0)
return;
final long timeout = System.currentTimeMillis() + msec;
cont = false;
DISPLAY.timerExec(msec, new Runnable() {
@Override
public void run() {
cont = true;
}
});
while (!cont) {
if (!DISPLAY.readAndDispatch()) {
DISPLAY.sleep();
}
}
}
public static void yield() {
while (DISPLAY.readAndDispatch()) {
// Do nothing
}
}
/**
* Posts the characters of the string one by one to the specified control.
*
* @param c
* the control
* @param s
* the string
*/
public static void postString(Control c, String s) {
for (int i = 0; i < s.length(); i++) {
postKeyStroke(c, s.substring(i, i + 1));
}
}
/**
* Posts the specified keystroke to the specified control which will get focus.
*
* @param c
* the control
* @param stroke
* the text representation of the key-stroke
*/
public static void postKeyStroke(Control c, String stroke) {
assertNotNull(c);
assertFalse(c.isDisposed());
KeyStroke keyStroke = null;
try {
keyStroke = KeyStroke.getInstance(stroke);
} catch (final ParseException ex) {
fail(stroke + ": " + ex.getMessage());
return;
}
assertTrue(stroke + ": not complete", keyStroke.isComplete());
// LogUtils.debug(c, stroke + " --> " + keyStroke);
Event event;
c.setFocus();
postModifierKeys(c, keyStroke, true);
event = new Event();
event.type = SWT.KeyDown;
event.stateMask = keyStroke.getModifierKeys();
event.keyCode = keyStroke.getNaturalKey();
event.character = (char) event.keyCode;
event.widget = c;
// System.out.println("e:: " + ToStringUtils.toString(event));
assertTrue(stroke + ": post KeyDown", c.getDisplay().post(event));
event = new Event();
event.type = SWT.KeyUp;
event.stateMask = keyStroke.getModifierKeys();
event.keyCode = keyStroke.getNaturalKey();
event.character = (char) event.keyCode;
event.widget = c;
// System.out.println("e:: " + ToStringUtils.toString(event));
assertTrue(stroke + ": post KeyUp", c.getDisplay().post(event));
postModifierKeys(c, keyStroke, false);
yield();
}
/**
* Posts a set of modifiers (shift, control, etc) to the specified control.
*
* @param c
* the {@link Control}
* @param keyStroke
* the key stroke
* @param down
* <code>true</code> for keyDown and <code>false</code> for keyUp
*/
public static void postModifierKeys(Control c, KeyStroke keyStroke, boolean down) {
if ((keyStroke.getModifierKeys() & SWT.COMMAND) == SWT.COMMAND) {
final Event event = new Event();
event.type = down ? SWT.KeyDown : SWT.KeyUp;
event.stateMask = 0;
event.keyCode = SWT.COMMAND;
event.widget = c;
assertTrue(c.getDisplay().post(event));
}
if ((keyStroke.getModifierKeys() & SWT.ALT) == SWT.ALT) {
final Event event = new Event();
event.type = down ? SWT.KeyDown : SWT.KeyUp;
event.stateMask = 0;
event.keyCode = SWT.ALT;
event.widget = c;
assertTrue(c.getDisplay().post(event));
}
if ((keyStroke.getModifierKeys() & SWT.SHIFT) == SWT.SHIFT) {
final Event event = new Event();
event.type = down ? SWT.KeyDown : SWT.KeyUp;
event.stateMask = 0;
event.keyCode = SWT.SHIFT;
event.widget = c;
assertTrue(c.getDisplay().post(event));
}
if ((keyStroke.getModifierKeys() & SWT.CTRL) == SWT.CTRL) {
final Event event = new Event();
event.type = down ? SWT.KeyDown : SWT.KeyUp;
event.stateMask = 0;
event.keyCode = SWT.CTRL;
event.widget = c;
assertTrue(c.getDisplay().post(event));
}
}
/**
* Posts the specified keystroke to the specified control which will get focus.
*
* @param c
* the control
* @param stroke
* the text representation of the key-stroke
* @param locator
* service locator used to find all relevant services
* @param handlerClass
* the expected handler class
*/
public static void postKeyStroke(Control c, final String stroke, IServiceLocator locator,
final Class<? extends IHandler> handlerClass) {
final ICommandService cs = (ICommandService) locator.getService(ICommandService.class);
final boolean[] executed = new boolean[] { false };
final IExecutionListener listener = new IExecutionListener() {
@Override
public void preExecute(String commandId, ExecutionEvent event) {
final IHandler handler = event.getCommand().getHandler();
assertNotNull("No handler active for " + event.getCommand().getId(), handler);
String hName = handler.toString();
if (hName.indexOf('@') >= 0) {
hName = hName.substring(0, hName.indexOf('@'));
}
// Class<? extends IHandler> hc = handler.getClass();
// if (hc == HandlerProxy.class) {
// hc = ((HandlerProxy) handler).getClass();
// }
if (!hName.equals("org.eclipse.ui.internal.MakeHandlersGo")) {
assertEquals("Stroke '" + stroke + "' command '" + commandId + "'", handlerClass.getName(), hName);
}
executed[0] = true;
}
@Override
public void postExecuteSuccess(String commandId, Object returnValue) {
// Nothing...
}
@Override
public void postExecuteFailure(String commandId, ExecutionException exception) {
fail(exception.getMessage());
}
@Override
public void notHandled(String commandId, NotHandledException exception) {
fail(exception.getMessage());
}
};
cs.addExecutionListener(listener);
postKeyStroke(c, stroke);
cs.removeExecutionListener(listener);
assertTrue("Stroke '" + stroke + "' has not been executed", executed[0]);
}
/**
* Posts a mouse event for the specified control and point.
*
* @param c
* the control
* @param p
* the point
*/
public static void postMouse(Control c, Point p) {
postMouse(c, p, 1);
}
/**
* Posts a mouse event for the specified control and point.
*
* @param c
* the control
*/
public static void postMouse(Control c) {
final Rectangle bounds = c.getBounds();
bounds.x = 0;
bounds.y = 0;
postMouse(c, bounds, 1);
}
/**
* Posts a mouse event for the specified control and point.
*
* @param c
* the control
* @param p
* the point
* @param noClicks
* the number of clicks
*/
public static void postMouse(Control c, Point p, int noClicks) {
postMouse(null, c, p, noClicks);
}
private static long lastMouseOperationExpireTime = 0;
/**
* Posts a mouse move event for the specified control and point.
*
* @param c
* the control
* @param p
* the point in the control
*/
public static void postMouseMove(Control c, Point p) {
final Point pt = c.getDisplay().map(c, null, p);
final Event e = new Event();
e.type = SWT.MouseMove;
e.x = pt.x;
e.y = pt.y;
assertTrue(c.getDisplay().post(e));
yield();
}
/**
* Posts a mouse event for the specified control and point.
*
* @param modifiers
* a key stroke specification - only the modifier part is used
* @param button
* the pressed button
* @param c
* the control
* @param noClicks
* the number of clicks
* @param p
* the point
*/
public static void postMouseDown(String modifiers, final int button, final Control c, final int noClicks) {
final long now = System.currentTimeMillis();
if (lastMouseOperationExpireTime > now) {
sleep((int) (lastMouseOperationExpireTime - now));
}
KeyStroke keyStroke = null;
try {
if (modifiers != null) {
keyStroke = KeyStroke.getInstance(modifiers);
assertTrue(keyStroke.isComplete());
}
} catch (final ParseException ex) {
fail(ex.getMessage());
}
if (keyStroke != null) {
c.setFocus();
postModifierKeys(c, keyStroke, true);
}
swtListen(new Runnable() {
@Override
public void run() {
final Event e = new Event();
for (int i = 1; i <= noClicks; i++) {
e.type = SWT.MouseDown;
e.widget = c;
e.button = button;
e.count = i;
// LogUtils.debug(e,
// "#" + i + ": " + ToStringUtils.toString(e));
assertTrue(c.getDisplay().post(e));
/*
* Problem on MACOSX: the event handler for the MouseDown event, seems to be actively waiting for
* the MouseUp event. While this happens, Display.readAndDispatch is blocked. Not event timer events
* are executed, only system events.
*
* The "solution" seems to be to avoid all waiting at this point!
*/
// sleep(50);
// yield();
e.type = SWT.MouseUp;
e.widget = c;
e.button = button;
e.count = i;
// LogUtils.debug(e,
// "#" + i + ": " + ToStringUtils.toString(e));
assertTrue(c.getDisplay().post(e));
// sleep(50);
}
}
});
yield();
if (keyStroke != null) {
postModifierKeys(c, keyStroke, false);
}
lastMouseOperationExpireTime = now + c.getDisplay().getDoubleClickTime() + 50;
}
/**
* Posts a mouse event for the specified control and point.
*
* @param modifiers
* a key stroke specification - only the modifier part is used
* @param c
* the control
* @param p
* the point
* @param noClicks
* the number of clicks
*/
public static void postMouse(final String modifiers, final Control c, final Point p, final int noClicks) {
postMouseMove(c, p);
postMouseDown(modifiers, 1, c, noClicks);
}
/**
* Posts a mouse event for the specified control and point.
*
* @param c
* the control
* @param bounds
* the bound of the area to click
*/
public static void postMouse(Control c, Rectangle bounds) {
postMouse(c, bounds, 1);
}
/**
* Posts a mouse event for the specified control and point
*
* @param c
* the control
* @param bounds
* the bound of the area to click
*/
public static void postMouseMove(Control c, Rectangle bounds) {
postMouseMove(c, new Point(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2));
}
/**
* Posts a mouse event for the specified table cell.
*
* @param t
* the table
* @param column
* the column number
* @param row
* the row number
*/
public static void postMouse(Table t, int column, int row) {
postMouse(t, t.getItem(row).getBounds(column), 1);
}
/**
* Posts a mouse event for the specified control and point.
*
* @param t
* the table
* @param column
* the column number
* @param row
* the row number
*/
public static void postMouseMove(Table t, int column, int row) {
postMouseMove(t, t.getItem(row).getBounds(column));
}
/**
* Posts a mouse event for the specified control and point
*
* @param t
* the table
* @param column
* the column number
* @param row
* the row number
* @param noClicks
* the number of clicks
*/
public static void postMouse(Table t, int column, int row, int noClicks) {
postMouse(t, t.getItem(row).getBounds(column), noClicks);
}
/**
* Posts a mouse event for the specified control and point.
*
* @param t
* the table
* @param column
* the column number
* @param row
* the row number
* @param noClicks
* the number of clicks
*/
public static void postMouse(String modifiers, Table t, int column, int row, int noClicks) {
postMouse(modifiers, t, t.getItem(row).getBounds(column), noClicks);
}
/**
* Posts a mouse event for the specified control and point.
*
* @param c
* the control
* @param bounds
* the bound of the area to click
* @param noClicks
* the number of clicks
*/
public static void postMouse(Control c, Rectangle bounds, int noClicks) {
postMouse(null, c, new Point(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2), noClicks);
}
/**
* Posts a mouse event for the specified control and point.
*
* @param c
* the control
* @param bounds
* the bound of the area to click
* @param noClicks
* the number of clicks
*/
public static void postMouse(String modifiers, Control c, Rectangle bounds, int noClicks) {
postMouse(modifiers, c, new Point(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2), noClicks);
}
protected static boolean cont = false;
private static final Display DISPLAY = Display.getDefault();
public final static Listener SWT_EVENT_LISTENER = new Listener() {
@Override
public void handleEvent(Event event) {
LogUtils.debug(this, TSSWTUtils.toString(event));
}
};
/**
* Runs the specified {@link Runnable} while debugging the SWT events.
*
* @param runnable
* the {@link Runnable} to run
*/
public static void swtListen(Runnable runnable) {
try {
for (int i = SWT.None; i < SWT.ImeComposition; i++) {
Display.getCurrent().addFilter(i, SWT_EVENT_LISTENER);
}
BaseTestUtils.assertNoLog(runnable);
} finally {
for (int i = SWT.None; i < SWT.ImeComposition; i++) {
Display.getCurrent().removeFilter(i, SWT_EVENT_LISTENER);
}
}
}
/**
* Tests that the pixel of the specified control have the specified RGB value.
*
* @param control
* the control
* @param x
* the x
* @param y
* the y
* @param expectedRGB
* the expected color
*/
public static void assertPixelColor(String what, Control control, int x, int y, RGB expectedRGB) {
/*
* Map to the shell to avoid negative coordinates
*/
final Display display = control.getDisplay();
final Shell shell = control.getShell();
final Point p = display.map(control, shell, new Point(x, y));
final GC gc = new GC(shell);
final Image image = new Image(display, 1, 1);
gc.copyArea(image, p.x, p.y);
gc.dispose();
final ImageData imageData = image.getImageData();
final int actualPixel = imageData.getPixel(0, 0);
final RGB actualRGB = imageData.palette.getRGB(actualPixel);
image.dispose();
if (!expectedRGB.equals(actualRGB)) {
dumpPixels(control, x, y, 3, 3, expectedRGB);
}
assertEquals(what, expectedRGB, actualRGB);
}
public static void dumpPixels(Control control, final int x, final int y, final int width, final int height,
RGB expectedRGB) {
/*
* Map to the shell to avoid negative coordinates
*/
final Display display = control.getDisplay();
final Shell shell = control.getShell();
final StringBuilder sb = new StringBuilder(400);
if (expectedRGB != null) {
sb.append("Expected " + expectedRGB);
}
final Point p = display.map(control, shell, new Point(x - width, y - height));
final GC gc = new GC(shell);
final Image image = new Image(display, width * 2 + 1, height * 2 + 1);
gc.copyArea(image, p.x - width, p.y - height);
final ImageData imageData = image.getImageData();
for (int dx = -width; dx < width; dx++) {
for (int dy = -height; dy < height; dy++) {
final int actualPixel = imageData.getPixel(dx + width, dy + height);
final RGB actualRGB = imageData.palette.getRGB(actualPixel);
sb.append("\n(" + dx + ";" + dy + "): " + actualRGB);
}
}
image.dispose();
gc.dispose();
// IPaintDecoration.Factory.paintRectangle(control, new Rectangle(x -
// width, y - height,
// width * 2, height * 2),
// display.getSystemColor(SWT.COLOR_RED));
yield();
LogUtils.debug(control, sb.toString());
sleep(500);
}
public static void assertPixelColorVerbose(Control control, int x, int y, RGB expectedRGB) {
/*
* Map to the shell to avoid negative coordinates
*/
final Display display = control.getDisplay();
final Shell shell = control.getShell();
final Composite parent = control.getParent();
//
final Point p2 = display.map(control, parent, x, y);
final GC gc2 = new GC(parent);
gc2.setForeground(display.getSystemColor(SWT.COLOR_BLUE));
gc2.drawRectangle(p2.x - 10, p2.y - 10, 21, 21);
gc2.drawLine(p2.x - 10, p2.y, p2.x + 10, p2.y);
gc2.drawLine(p2.x, p2.y - 10, p2.x, p2.y + 10);
gc2.dispose();
sleep(1003);
for (int dx = -5; dx <= 5; dx++) {
for (int dy = -5; dy <= 5; dy++) {
final Point p = display.map(control, shell, new Point(x + dx, y + dx));
final GC gc = new GC(shell);
final Image image = new Image(display, 1, 1);
gc.copyArea(image, p.x, p.y);
gc.dispose();
final ImageData imageData = image.getImageData();
final int actualPixel = imageData.getPixel(0, 0);
final RGB actualRGB = imageData.palette.getRGB(actualPixel);
System.out.println("(" + dx + ";" + dy + "): " + actualRGB);
image.dispose();
}
}
// assertEquals(expectedRGB, actualRGB);
}
/**
* Waits until the Workbench have been fully loaded and a workbench window is active.
*/
public static void waitForWorkbench() {
final IWorkbench workbench = PlatformUI.getWorkbench();
while (workbench.getActiveWorkbenchWindow() == null) {
sleep(100);
}
}
}