/*******************************************************************************
* 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.internal.operation;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.swing.KeyStroke;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Event;
import com.windowtester.internal.debug.Logger;
import com.windowtester.internal.runtime.WTLocale;
import com.windowtester.runtime.WT;
import com.windowtester.runtime.internal.concurrent.VoidCallable;
import com.windowtester.runtime.swt.internal.text.KeyMapTextEntryStrategy.UnMappedKeyException;
import com.windowtester.runtime.swt.internal.text.KeyStrokeMap;
// This class is derived from three ITextEntryStrategy classes:
// * DelegatingTextEntryStrategy,
// * UIDriverTextEntryStrategy,
// * KeyMapTextEntryStrategy
/**
* Enter text and trigger commands by pushing key down and key up events onto the OS event
* queue. Both {@link #keyString(String)} and {@link #queueChar(char)} translate
* characters based upon the current locale and calls to {@link WT#setLocaleToCurrent()}
* and {@link WT#resetLocale()}. {@link #keyCode(int)} does not translate characters.
*/
public class SWTKeyOperation extends SWTOperation
{
private static final VoidCallable SYNC_DELAY = new VoidCallable() {
public void call() throws Exception {
// Do nothing... just for synchronization
}
};
private static final char[] ACCENT_CHARS = new char[]{
0, '^', '�', '`', '�', '~'
};
private static final Map<Integer, Character> VOWEL_CHARS;
static
{
Map<Integer, Character>tempMap = new HashMap<Integer, Character>();
tempMap.put(65, 'a');
tempMap.put(69, 'e');
tempMap.put(73, 'i');
tempMap.put(79, 'o');
tempMap.put(85, 'u');
VOWEL_CHARS = Collections.unmodifiableMap(tempMap);
}
/**
* The number of queued OS keystroke events
*/
private int eventCount = 0;
/**
* Queue key click events (key down followed by key up) for the characters in the
* specified string. The events generated are dependent upon the current locale and
* calls to {@link WT#setLocaleToCurrent()} and {@link WT#resetLocale()}.
*
* @param txt the string of characters
* @return this operation so that calls can be cascaded on a single line such as
* <code>new SWTKeyOperation().keyCode(WT.HOME).execute();</code>
*/
public SWTKeyOperation keyString(String txt) {
int txtLen = txt.length();
for (int i = 0; i < txtLen; i++)
queueChar(txt.charAt(i));
return this;
}
/**
* Queue key click events (key down followed by key up) for the specified keyCode.
* Events generated are NOT dependent upon upon the current locale. All uppercase
* characters (e.g. 'T') are converted to lower case (e.g. 't') thus
* <code>keyCode('T')</code> is equivalent to <code>keyCode('t')</code>. If you want
* an upper case character, then call this method with {@link WT#SHIFT} as in
* <code>keyCode(WT.SHIFT | 'T')</code> or <code>keyCode(WT.SHIFT | 't')</code>.
*
* @param keyCode the code for the key to be queued such as {@link WT#HOME},
* {@link WT#CTRL} | 't', {@link WT#SHIFT} | {@link WT#END}
* @return this operation so that calls can be cascaded on a single line such as
* <code>new SWTKeyOperation().keyCode(WT.HOME).execute();</code>
*/
public SWTKeyOperation keyCode(int keyCode) {
queueModifierKeysDown(keyCode);
int unmodified = keyCode - (keyCode & WT.MODIFIER_MASK);
// Key code characters have the SWT.KEYCODE_BIT bit set
// whereas unicode characters do not
if ((unmodified | SWT.KEYCODE_BIT) != 0) {
queueKeyCodeDown(unmodified);
queueKeyCodeUp(unmodified);
}
else {
queueCharDown((char) unmodified);
queueCharUp((char) unmodified);
}
queueModifierKeysUp(keyCode);
return this;
}
//=======================================================================
// Internal setup
/**
* Queue key click events (key down followed by key up) for the specified character.
* The events generated are dependent upon the current locale and calls to
* {@link WT#setLocaleToCurrent()} and {@link WT#resetLocale()}. If you want to queue
* events for characters with accelerators such as {@link WT#CTRL} | 's', call
* {@link #keyCode(int)} rather than this method.
*
* @param ch the character without accelerators
*/
private void queueChar(char ch) {
// US English keyboard
if (!WTLocale.isCurrent) {
boolean shift = needsShift(ch);
if (shift)
queueKeyCodeDown(WT.SHIFT);
queueCharDown(ch);
queueCharUp(ch);
if (shift)
queueKeyCodeUp(WT.SHIFT);
return;
}
// Get locale specific keystroke
// Using the keymaps here
KeyStroke ks = KeyStrokeMap.getKeyStroke(ch);
// If not found, then use default mapping to get keystroke
if (ks == null)
ks = KeyStrokeMap.getDefaultKeyStroke(ch);
// If still not found, then throw an exception
if (ks == null)
throw new UnMappedKeyException("Key map not found for " + ch + " for locale "
+ Locale.getDefault().toString());
// check if key is an accent char
// these are generated by using two key press
// the dead key followed by a vowel
int accentKey = KeyStrokeMap.getAccentKey(ch);
if (accentKey > 0) {
// get the dead key for the accent char
char accentCh = ACCENT_CHARS[accentKey];
// key press for dead key
queueKeystroke(accentCh, KeyStrokeMap.getKeyStroke(accentCh),false);
// key press for the vowel
queueKeystroke(ch, ks,true);
}
else queueKeystroke(ch, ks,false);
}
/**
* Queue the key events for the specified keystroke
*
* @param ch the character
* @param ks the keystroke for the character
* @param isVowel indicate whether the key press is the vowel for the accent char
*/
private void queueKeystroke(char ch, KeyStroke ks,boolean isVowel) {
int keyCode = ks.getKeyCode();
int mod = ks.getModifiers();
boolean ctrl = keyCode == KeyEvent.VK_CONTROL || (mod & InputEvent.CTRL_MASK) != 0;
boolean alt = keyCode == KeyEvent.VK_ALT || (mod & InputEvent.ALT_MASK) != 0;
boolean shift = keyCode == KeyEvent.VK_SHIFT || (mod & InputEvent.SHIFT_MASK) != 0;
StringBuilder sb = new StringBuilder();
sb.append("keyChar translated '").append(ch);
sb.append("' into keycode=").append(keyCode);
if (ctrl)
sb.append(" ctrl");
if (alt)
sb.append(" alt");
if (shift)
sb.append(" shift");
Logger.log(sb.toString());
if (ctrl)
queueKeyCodeDown(WT.CTRL);
if (alt)
queueKeyCodeDown(WT.ALT);
if (shift)
queueKeyCodeDown(WT.SHIFT);
// get the vowel to be pressed using the keycode
if (isVowel){
ch = VOWEL_CHARS.get(keyCode);
}
queueCharDown(ch);
queueCharUp(ch);
if (shift)
queueKeyCodeUp(WT.SHIFT);
if (alt)
queueKeyCodeUp(WT.ALT);
if (ctrl)
queueKeyCodeUp(WT.CTRL);
}
/**
* Determine if this key requires a shift to dispatch the keyStroke.
*
* @param keyCode - the key in question
* @return true if a shift event is required.
*/
public boolean needsShift(char keyCode) {
if (keyCode >= 62 && keyCode <= 90)
return true;
if (keyCode >= 123 && keyCode <= 126)
return true;
if (keyCode >= 33 && keyCode <= 43 && keyCode != 39)
return true;
if (keyCode >= 94 && keyCode <= 95)
return true;
if (keyCode == 58 || keyCode == 60 || keyCode == 62)
return true;
return false;
}
/**
* Override the superclass implementation to slow down the event generation just
* slightly because on the mac, the first character in a long sequence of characters
* gets lost and causes that character to appear out of order in the text.
*/
protected void queueOSEvent(Event event) {
super.queueOSEvent(event);
// 1/100 second pause after each key event - see case 43861
if (++eventCount < 5)
queueStep(null);
}
//=======================================================================
// Execution
/**
* Override the superclass implementation to add a brief synchronization delay before
* pushing keystroke events on the OS event queue
*/
public void execute() {
// try {
// displayRef.execute(SYNC_DELAY, 300);
// }
// catch (WaitTimedOutException e) {
// // Ignore timeout because may be blocked on native dialog call
// }
super.execute();
}
/**
* Override the superclass implementation to push keystroke events from the test
* thread rather than the UI thread so that we can drive native dialogs.
*
* @return <code>true</code> if execution is complete, or <code>false</code> if
* {@link #executeInUI()} should be called again to finish executing after a
* brief delay up to the maximum number of retries.
*/
protected boolean executeCallable(int maxWaitTime) {
// try {
// return executeInUI();
// }
// catch (RuntimeException e) {
// throw e;
// }
// catch (Exception e) {
// throw new RuntimeException(e);
// }
return super.executeCallable(maxWaitTime);
}
}