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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user