/*******************************************************************************
* Copyright (c) 2007, 2014 compeople AG 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:
* compeople AG - initial API and implementation
*******************************************************************************/
package org.eclipse.riena.internal.ui.swt.test;
import java.util.Locale;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.riena.core.util.Nop;
import org.eclipse.riena.ui.swt.facades.SWTFacade;
/**
* Utility class for UI tests.
*/
public final class UITestHelper {
/** Keycode for 'arrow down' (16777218) */
public static final int KC_ARROW_DOWN = 16777218;
/** Keycode for 'arrow up' (16777217) */
public static final int KC_ARROW_UP = 16777217;
/** Keycode for 'home' (16777223) */
public static final int KC_HOME = 16777223;
/** Keycode for 'end' (16777224) */
public static final int KC_END = 16777224;
/** Keycode for 'del' (127) */
public static final int KC_DEL = 127;
private UITestHelper() {
// prevent instantiation
}
public static void fireSelectionEvent(final Widget widget) {
final Event event = new Event();
event.type = SWT.Selection;
event.widget = widget;
widget.notifyListeners(SWT.Selection, event);
}
public static void readAndDispatch(final Widget control) {
while (!control.isDisposed() && control.getDisplay().readAndDispatch()) {
Nop.reason("keep going"); //$NON-NLS-1$
}
}
/**
* Send a key event with the given keycode.
*
* @param display
* a non-null {@link Display} instance
* @param keyCode
* a SWT key code
*/
public static void sendKeyAction(final Display display, final int keyCode) {
final EventSender sender = new EventSender(display, keyCode);
send(display, sender);
}
/**
* Send the give message as a series of key events
*
* @param display
* a non-null {@link Display} instance
* @param message
* a non-null String
*/
public static void sendString(final Display display, final String message) {
final EventSender sender = new EventSender(display, message);
send(display, sender);
}
private static void send(final Display display, final Runnable runnable) {
final Thread thread = new Thread(runnable);
final Shell activeShell = display.getActiveShell();
if (activeShell != null) {
activeShell.forceActive();
}
thread.start();
waitAndDispatch(display, thread);
}
private static void waitAndDispatch(final Display display, final Thread thread) {
final Shell shell = display.getActiveShell();
while (!shell.isDisposed() && thread.isAlive()) {
try {
display.readAndDispatch();
} catch (final SWTException exc) {
// swallow this kind of exception
if (!exc.toString().contains("Workbench has not been created yet.")) { //$NON-NLS-1$
throw exc;
}
}
}
}
/**
* Wraps and sends keyboard events to the shell.
*/
private static class EventSender implements Runnable {
private static final int MS_SHORT_WAIT = 10;
private static final int MS_LONG_WAIT = 250;
private final Display display;
private final int keyCode;
private final String message;
private final SWTFacade swtFacade;
EventSender(final Display display, final int keyCode) {
this.display = display;
this.keyCode = keyCode;
this.message = null;
this.swtFacade = SWTFacade.getDefault();
}
EventSender(final Display display, final String message) {
this.display = display;
this.keyCode = 0;
this.message = message;
this.swtFacade = SWTFacade.getDefault();
}
public void run() {
if (message != null) {
sendMessage();
} else {
sendKeyEvent();
}
}
/**
* Posting a character key-down event apparently simulates pressing the
* <em>physical-keyboard</em> key that is bound to the character.
*
* On Win32 at least, the OS's input manager for switching between
* different logical keyboard layouts is ignored. So with a physical
* QWERTZ keyboard, '/' is invariably sent as '7', and '&' as '6'.
*/
private void sendMessage() {
for (int i = 0; i < message.length(); i++) {
final char ch = message.charAt(i);
final boolean isShift = doShift(ch);
if (isShift) {
postShift(true);
}
postCharacter(ch);
if (isShift) {
postShift(false);
}
}
}
/**
* @param ch
* character to decide upon
* @return whether the character necessitates sending 'Shift' KeyDown/Up
* events
*/
private boolean doShift(final char ch) {
boolean result = false;
if (Character.isLetter(ch)) {
result = Character.isUpperCase(ch);
} else if (ch == '/' && "US" != Locale.getDefault().getCountry()) { //$NON-NLS-1$
result = true;
}
return result;
}
private void sendKeyEvent() {
final Event event = new Event();
event.type = SWT.KeyDown;
event.keyCode = keyCode;
doSleep(MS_LONG_WAIT);
post(event);
doSleep(MS_LONG_WAIT);
event.type = SWT.KeyUp;
post(event);
doSleep(MS_LONG_WAIT);
}
private void postCharacter(final char ch) {
final Event event = new Event();
event.type = SWT.KeyDown;
event.character = ch;
doSleep(MS_LONG_WAIT);
post(event);
doSleep(MS_LONG_WAIT);
event.type = SWT.KeyUp;
post(event);
doSleep(MS_LONG_WAIT);
}
private void postShift(final boolean keyDown) {
final Event event = new Event();
event.keyCode = SWT.SHIFT;
event.type = keyDown ? SWT.KeyDown : SWT.KeyUp;
post(event);
doSleep(MS_SHORT_WAIT);
}
private boolean post(final Event event) {
return swtFacade.postEvent(display, event);
}
private void doSleep(final int millis) {
try {
Thread.sleep(millis);
} catch (final InterruptedException iex) {
Nop.reason("ignore"); //$NON-NLS-1$
}
}
}
}