package com.limegroup.gnutella.gui.tables;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.table.TableModel;
import javax.swing.text.Position;
/**
* Listener to key-typed events, to move focus to the closest possible match
* in the model.
*
* This works exactly like (and is modelled off of) JList's BasicListUI's
* Handler.
*/
public class KeyTypedMover implements KeyListener {
private String prefix = "";
private String typedString = "";
private long lastTime = 0L;
/**
* The time factor to treate the series of typed alphanumeric key
* as prefix for first letter navigation.
*/
private final long timeFactor = 500L;
/**
* Invoked when a key has been typed.
*
* Moves the keyboard focus to the first element whose prefix matches the
* sequence of alphanumeric keys pressed by the user with delay less
* than value of <code>timeFactor</code>.
* Subsequent same key presses move the keyboard
* focus to the next object that starts with the same letter until another
* key is pressed, then it is treated as the prefix with appropriate number
* of the same letters followed by first typed anothe letter.
*/
public void keyTyped(KeyEvent e) {
LimeJTable src = (LimeJTable)e.getSource();
TableModel model = src.getModel();
if (model.getRowCount() == 0 ||
e.isAltDown() || e.isControlDown() || e.isMetaDown() ||
isNavigationKey(e)) {
// Nothing to select
return;
}
boolean startingFromSelection = true;
char c = e.getKeyChar();
long time = e.getWhen();
int startIndex = src.getSelectionModel().getLeadSelectionIndex();
if (time - lastTime < timeFactor) {
typedString += c;
if((prefix.length() == 1) && (c == prefix.charAt(0))) {
// Subsequent same key presses move the keyboard focus to the next
// object that starts with the same letter.
startIndex++;
} else {
prefix = typedString;
}
} else {
startIndex++;
typedString = "" + c;
prefix = typedString;
}
lastTime = time;
if (startIndex < 0 || startIndex >= model.getRowCount()) {
startingFromSelection = false;
startIndex = 0;
}
int index = src.getNextMatch(prefix, startIndex, Position.Bias.Forward);
if (index >= 0) {
src.setSelectedRow(index);
src.ensureRowVisible(index);
} else if (startingFromSelection) { // wrap
index = src.getNextMatch(prefix, 0, Position.Bias.Forward);
if (index >= 0) {
src.setSelectedRow(index);
src.ensureRowVisible(index);
}
}
}
/**
* Invoked when a key has been pressed.
*
* Checks to see if the key event is a navigation key to prevent
* dispatching these keys for the first letter navigation.
*/
public void keyPressed(KeyEvent e) {
if ( isNavigationKey(e) ) {
prefix = "";
typedString = "";
lastTime = 0L;
}
}
public void keyReleased(KeyEvent e) {}
/**
* Returns whether or not the supplied key event maps to a key that is used for
* navigation. This is used for optimizing key input by only passing non-
* navigation keys to the first letter navigation mechanism.
*/
private boolean isNavigationKey(KeyEvent event) {
InputMap inputMap =
((JComponent)event.getSource()).getInputMap(
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
return (inputMap != null && inputMap.get(key) != null);
}
}