In japan, people will frequently run with keyboards in a mode

that generate wide versions of the latin characters, which has
the nasty side effect of sending keyTyped events instead of 
keyPressed and keyReleased events.

This extends the KeyboardManager to be able to cope with such
situations, doing sane things in that case. Unfortunately, since
you only get one event for the character, we don't really know
when it went down and when it came up, so we fake it by turning
native keyboard repeat on and pretending the key was held down
for one repeat's worth of time. While extreme values on the 
system's key repeat or the desired KeyTranslator's repeat rate
could give unfortunate behavior, with sane values like people
typically should be using, it should all work. Swordfighting
and sailing (the two big users of keyPressed & keyReleased events
in yohoho) seem to be completely playable for me when running like
this.


git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@580 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Dave Hoover
2008-07-29 00:13:22 +00:00
parent 3fad5fb569
commit 719f63577b
3 changed files with 201 additions and 51 deletions
@@ -35,24 +35,48 @@ public interface KeyTranslator
* or both.
*/
public boolean hasCommand (int keyCode);
/**
* Returns whether there is an action command for the key corresponding to the given character
* in the case of a keyTyped event corresponding to it.
*/
public boolean hasCommand (char ch);
/**
* Returns the action command string associated with a key press of the key corresponding to
* the given key code, or <code>null</code> if there is no associated command.
*/
public String getPressCommand (int keyCode);
/**
* Returns the action command string associated with a key press of the given character,
* or <code>null</code> if there is no associated command.
*/
public String getPressCommand (char ch);
/**
* Returns the action command string associated with a key release of the key corresponding to
* the given key code, or <code>null</code> if there is no associated command.
*/
public String getReleaseCommand (int keyCode);
/**
* Returns the action command string associated with a key release of the given character,
* or <code>null</code> if there is no associated command.
*/
public String getReleaseCommand (char ch);
/**
* Returns the number of times each second that key presses are to be automatically repeated
* while the key is held down, or <code>0</code> to disable auto-repeat for the key.
*/
public int getRepeatRate (int keyCode);
/**
* Returns the number of times each second that key presses are to be automatically repeated
* while the key is held down, or <code>0</code> to disable auto-repeat for the key.
*/
public int getRepeatRate (char ch);
/**
* Returns the delay in milliseconds before generating auto-repeated key press events for the
@@ -60,6 +84,12 @@ public interface KeyTranslator
*/
public long getRepeatDelay (int keyCode);
/**
* Returns the delay in milliseconds before generating auto-repeated key press events for the
* specified key.
*/
public long getRepeatDelay (char ch);
/**
* Returns an iterator that iterates over the available press commands.
*/
@@ -22,20 +22,21 @@
package com.threerings.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import com.samskivert.util.HashIntMap;
/**
* A basic implementation of the {@link KeyTranslator} interface that
* provides facilities for mapping key codes to action command strings for
* use by the {@link KeyboardManager}.
* A basic implementation of the {@link KeyTranslator} interface that provides facilities for
* mapping key codes to action command strings for use by the {@link KeyboardManager}.
*/
public class KeyTranslatorImpl implements KeyTranslator
public class KeyTranslatorImpl
implements KeyTranslator
{
/**
* Adds a mapping from a key press to an action command string that
* will auto-repeat at a default repeat rate.
* Adds a mapping from a key press to an action command string that will auto-repeat at a
* default repeat rate.
*/
public void addPressCommand (int keyCode, String command)
{
@@ -43,14 +44,12 @@ public class KeyTranslatorImpl implements KeyTranslator
}
/**
* Adds a mapping from a key press to an action command string that
* will auto-repeat at the specified repeat rate. Overwrites any
* existing mapping and repeat rate that may have already been
* registered.
*
* @param rate the number of times each second that the key press
* should be repeated while the key is down, or <code>0</code> to
* disable auto-repeat for the key.
* Adds a mapping from a key press to an action command string that will auto-repeat at the
* specified repeat rate. Overwrites any existing mapping and repeat rate that may have
* already been registered.
*
* @param rate the number of times each second that the key press should be repeated while the
* key is down, or <code>0</code> to disable auto-repeat for the key.
*/
public void addPressCommand (int keyCode, String command, int rate)
{
@@ -58,16 +57,14 @@ public class KeyTranslatorImpl implements KeyTranslator
}
/**
* Adds a mapping from a key press to an action command string that
* will auto-repeat at the specified repeat rate after the specified
* auto-repeat delay has expired. Overwrites any existing mapping for
* the specified key code that may have already been registered.
*
* @param rate the number of times each second that the key press
* should be repeated while the key is down; passing <code>0</code>
* will result in no repeating.
* @param repeatDelay the delay in milliseconds before auto-repeating
* key press events will be generated for the key.
* Adds a mapping from a key press to an action command string that will auto-repeat at the
* specified repeat rate after the specified auto-repeat delay has expired. Overwrites any
* existing mapping for the specified key code that may have already been registered.
*
* @param rate the number of times each second that the key press should be repeated while the
* key is down; passing <code>0</code> will result in no repeating.
* @param repeatDelay the delay in milliseconds before auto-repeating key press events will be
* generated for the key.
*/
public void addPressCommand (int keyCode, String command, int rate, long repeatDelay)
{
@@ -78,8 +75,8 @@ public class KeyTranslatorImpl implements KeyTranslator
}
/**
* Adds a mapping from a key release to an action command string.
* Overwrites any existing mapping that may already have been registered.
* Adds a mapping from a key release to an action command string. Overwrites any existing
* mapping that may already have been registered.
*/
public void addReleaseCommand (int keyCode, String command)
{
@@ -88,8 +85,8 @@ public class KeyTranslatorImpl implements KeyTranslator
}
/**
* Returns the key record for the specified key, creating it and
* inserting it in the key table if necessary.
* Returns the key record for the specified key, creating it and inserting it in the key table
* if necessary.
*/
protected KeyRecord getKeyRecord (int keyCode)
{
@@ -107,12 +104,25 @@ public class KeyTranslatorImpl implements KeyTranslator
return (_keys.get(keyCode) != null);
}
// documentation inherited from interface KeyTranslator
public boolean hasCommand (char ch)
{
return (_charCommands.get(ch) != null);
}
// documentation inherited from interface KeyTranslator
public String getPressCommand (int keyCode)
{
KeyRecord krec = _keys.get(keyCode);
return (krec == null) ? null : krec.pressCommand;
}
// documentation inherited from interface KeyTranslator
public String getPressCommand (char ch)
{
KeyRecord krec = _charCommands.get(ch);
return (krec == null) ? null : krec.pressCommand;
}
// documentation inherited from interface KeyTranslator
public String getReleaseCommand (int keyCode)
@@ -120,6 +130,13 @@ public class KeyTranslatorImpl implements KeyTranslator
KeyRecord krec = _keys.get(keyCode);
return (krec == null) ? null : krec.releaseCommand;
}
// documentation inherited from interface KeyTranslator
public String getReleaseCommand (char ch)
{
KeyRecord krec = _charCommands.get(ch);
return (krec == null) ? null : krec.releaseCommand;
}
// documentation inherited from interface KeyTranslator
public int getRepeatRate (int keyCode)
@@ -127,6 +144,13 @@ public class KeyTranslatorImpl implements KeyTranslator
KeyRecord krec = _keys.get(keyCode);
return (krec == null) ? DEFAULT_REPEAT_RATE : krec.repeatRate;
}
// documentation inherited from interface KeyTranslator
public int getRepeatRate (char ch)
{
KeyRecord krec = _charCommands.get(ch);
return (krec == null) ? DEFAULT_REPEAT_RATE : krec.repeatRate;
}
// documentation inherited from interface KeyTranslator
public long getRepeatDelay (int keyCode)
@@ -134,6 +158,13 @@ public class KeyTranslatorImpl implements KeyTranslator
KeyRecord krec = _keys.get(keyCode);
return (krec == null) ? DEFAULT_REPEAT_DELAY : krec.repeatDelay;
}
// documentation inherited from interface KeyTranslator
public long getRepeatDelay (char ch)
{
KeyRecord krec = _charCommands.get(ch);
return (krec == null) ? DEFAULT_REPEAT_DELAY : krec.repeatDelay;
}
// documentation inherited from interface KeyTranslator
public Iterator<String> enumeratePressCommands ()
@@ -142,7 +173,7 @@ public class KeyTranslatorImpl implements KeyTranslator
for (KeyRecord rec : _keys.values()) {
commands.add(rec.pressCommand);
}
return commands.iterator();
}
@@ -153,7 +184,7 @@ public class KeyTranslatorImpl implements KeyTranslator
for (KeyRecord rec : _keys.values()) {
commands.add(rec.releaseCommand);
}
return commands.iterator();
}
@@ -164,18 +195,25 @@ public class KeyTranslatorImpl implements KeyTranslator
/** The command to be posted when the key is released. */
public String releaseCommand;
/** The rate in presses per second at which the key is to be auto-repeated. */
public int repeatRate;
/** The delay in milliseconds that must expire with the key still
* pressed before auto-repeated key presses will begin. */
/**
* The delay in milliseconds that must expire with the key still pressed before
* auto-repeated key presses will begin.
*/
public long repeatDelay;
}
/** The keys for which commands are registered. */
protected HashIntMap<KeyRecord> _keys = new HashIntMap<KeyRecord>();
/**
* Any commands we wish to perform upon key typed events for characters.
*/
protected HashMap<Character,KeyRecord> _charCommands = new HashMap<Character,KeyRecord>();
/** The default key press repeat rate. */
protected static final int DEFAULT_REPEAT_RATE = 5;
@@ -21,14 +21,15 @@
package com.threerings.util;
import static com.threerings.NenyaLog.log;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.util.HashMap;
import java.util.Iterator;
import javax.swing.JComponent;
@@ -41,11 +42,8 @@ import com.samskivert.util.HashIntMap;
import com.samskivert.util.Interval;
import com.samskivert.util.ObserverList;
import com.samskivert.util.RunAnywhere;
import com.threerings.util.keybd.Keyboard;
import static com.threerings.NenyaLog.log;
/**
* The keyboard manager observes keyboard actions on a particular component and posts commands
* associated with the key presses to the {@link Controller} hierarchy. It allows specifying the
@@ -78,8 +76,7 @@ public class KeyboardManager
public KeyboardManager ()
{
// capture low-level keyboard events via the keyboard focus manager
KeyboardFocusManager.getCurrentKeyboardFocusManager().
addKeyEventDispatcher(this);
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this);
}
/**
@@ -187,9 +184,10 @@ public class KeyboardManager
// note whether key auto-repeating was enabled
_nativeRepeat = Keyboard.isKeyRepeatEnabled();
// disable native key auto-repeating so that we can
// definitively ascertain key pressed/released events
Keyboard.setKeyRepeat(false);
// Disable native key auto-repeating so that we can definitively ascertain key
// pressed/released events.
// Or not, if we've discovered we don't want to.
Keyboard.setKeyRepeat(!_shouldDisableNativeRepeat);
}
}
@@ -270,6 +268,9 @@ public class KeyboardManager
case KeyEvent.KEY_RELEASED:
return keyReleased(e);
case KeyEvent.KEY_TYPED:
return keyTyped(e);
default:
return false;
}
@@ -305,7 +306,47 @@ public class KeyboardManager
return hasCommand;
}
/**
* Called when Swing notifies us that a key has been typed while the
* keyboard manager is active.
*
* @return true to swallow the key event
*/
protected boolean keyTyped (KeyEvent e)
{
logKey("keyTyped", e);
// get the action command associated with this key
char keyChar = e.getKeyChar();
boolean hasCommand = _xlate.hasCommand(keyChar);
if (hasCommand) {
// Okay, we're clearly doing actions based on key typing, so we're going to need native
// keyboard repeating turned on. Oh well.
if (_shouldDisableNativeRepeat) {
_shouldDisableNativeRepeat = false;
if (Keyboard.isAvailable()) {
Keyboard.setKeyRepeat(!_shouldDisableNativeRepeat);
}
}
KeyInfo info = _chars.get(keyChar);
if (info == null) {
info = new KeyInfo(keyChar);
_keys.put(keyChar, info);
}
// remember the last time this key was pressed
info.setPressTime(RunAnywhere.getWhen(e));
}
// notify any key observers of the key press
notifyObservers(KeyEvent.KEY_TYPED, e.getKeyChar(), RunAnywhere.getWhen(e));
return hasCommand;
}
/**
* Called when Swing notifies us that a key has been released while
* the keyboard manager is active.
@@ -407,6 +448,20 @@ public class KeyboardManager
_pressDelay = (rate == 0) ? 0 : (1000L / rate);
_repeatDelay = _xlate.getRepeatDelay(_keyCode);
}
/**
* Constructs a key info object for the given character.
*/
public KeyInfo (char keyChar)
{
_keyChar = keyChar;
_keyText = KeyEvent.getKeyText(_keyChar);
_pressCommand = _xlate.getPressCommand(_keyChar);
_releaseCommand = _xlate.getReleaseCommand(_keyChar);
int rate = _xlate.getRepeatRate(_keyChar);
_pressDelay = (rate == 0) ? 0 : (1000L / rate);
_repeatDelay = _xlate.getRepeatDelay(_keyChar);
}
/**
* Sets the last time the key was pressed.
@@ -536,8 +591,15 @@ public class KeyboardManager
// }
} else if (_lastPress != 0 && _pressCommand != null) {
// post the key press command again
postPress(now);
if (_keyCode != KeyEvent.VK_UNDEFINED) {
// post the key press command again
postPress(now);
} else {
// We're dealing with a key typed event, so we don't really know what's going
// on, so we'll pretend we released it now, and hope the native keyboard repeat
// takes care of us.
postRelease(now);
}
}
}
@@ -582,7 +644,11 @@ public class KeyboardManager
*/
protected void postPress (long timestamp)
{
notifyObservers(KeyEvent.KEY_PRESSED, _keyCode, timestamp);
if (_keyCode != KeyEvent.VK_UNDEFINED) {
notifyObservers(KeyEvent.KEY_PRESSED, _keyCode, timestamp);
} else {
notifyObservers(KeyEvent.KEY_TYPED, _keyChar, timestamp);
}
Controller.postAction(_target, _pressCommand);
}
@@ -591,7 +657,11 @@ public class KeyboardManager
*/
protected void postRelease (long timestamp)
{
notifyObservers(KeyEvent.KEY_RELEASED, _keyCode, timestamp);
if (_keyCode != KeyEvent.VK_UNDEFINED) {
notifyObservers(KeyEvent.KEY_RELEASED, _keyCode, timestamp);
} else {
// TODO: Something telling observers about our character somehow?
}
Controller.postAction(_target, _releaseCommand);
}
@@ -619,8 +689,11 @@ public class KeyboardManager
/** A text representation of this key. */
protected String _keyText;
/** The key code associated with this key info object. */
protected int _keyCode;
/** The key code associated with this key info object, if any. */
protected int _keyCode = KeyEvent.VK_UNDEFINED;
/** The character associated with this key info object, if any. */
protected char _keyChar;
/** The milliseconds to sleep between sending repeat key commands. */
protected long _pressDelay;
@@ -672,6 +745,9 @@ public class KeyboardManager
/** A hashtable mapping key codes to {@link KeyInfo} objects. */
protected HashIntMap<KeyInfo> _keys = new HashIntMap<KeyInfo>();
/** A hashtable mapping characters to {@link KeyInfo} objects. */
protected HashMap<Character,KeyInfo> _chars = new HashMap<Character,KeyInfo>();
/** Whether the keyboard manager currently has the keyboard focus. */
protected boolean _focus;
@@ -698,4 +774,10 @@ public class KeyboardManager
/** Whether native key auto-repeating was enabled when the keyboard manager was last enabled. */
protected boolean _nativeRepeat;
/** Whether we want to disable native key auto-repeating. If we're dealing with wacky keys that
* send only key typed events, we might need to fall back to letting that happen so things work
* right.
*/
protected boolean _shouldDisableNativeRepeat = true;
}