/*
Wotonomy: OpenStep design patterns for pure Java applications.
Copyright (C) 2000 Blacksmith, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see http://www.gnu.org
*/
package net.wotonomy.ui.swing.components;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.KeyEvent;
import javax.swing.JTextField;
/**
* SmartTextField is an abstract class for that allows the text field to
* intelligently analyze the user's input in real-time. As the user enters
* keystrokes, the generated string is analyzed to determine if the new string
* is valid based on the criteria of the concrete classes that extend this
* class. An invalid keystroke is rejected and not displayed in the text field.
* This class can be extended to to create smart text fields that only accept
* integers or floating points number or alphabetic strings of maximum length.
* These are several examples.
*
* @author rob@straylight.princeton.com
* @author $Author: cgruber $
* @version $Revision: 904 $
*/
public abstract class SmartTextField extends JTextField {
/*******************************
* CONSTANTS
*******************************/
private static final int BACKSPACE = 8;
private static final int DELETE = 127;
private static final int SPACE = 32;
private static final int DASH = 45;
private static final int UNDERSCORE = 95;
private static final int PERIOD = 46;
private static final int PASTE = 22; // Ctl-V
/*******************************
* PUBLIC METHODS
*******************************/
/**
* This method processes a key event. This event is generated by input from the
* keyboard when this text field has the focus. This method is called for every
* key that is pressed and released on the keyboard. This includes modifier keys
* like the shift and alt keys. This class looks at the key and determines if
* the key is valid input given the restrictions of the concrete sub-classes.
*
*
* Example - A smart text field only allows alphabetic characters. If the key
* pressed is a "2" then this method will determine that the key is invalid and
* "consume" the key event.
*
* Note - Every printable character has a "TYPED" key event. Currentlt under
* Java 1.2.1 this does not happen. Bug 4186905 relating this bug has been fixed
* and is awaiting release.
*
* @param e A key event generated by a keyboard action.
*/
public void processKeyEvent(KeyEvent e) {
String currentText = "";
String testString = "";
char newChar = e.getKeyChar();
int currentLength = 0;
int selectionStart = 0;
int selectionEnd = 0;
int endOfHead = 0;
int startOfTail = 0;
boolean backspace = false;
boolean delete = false;
boolean paste = false;
boolean insertionPoint = false;
boolean selectionAtStart = false;
boolean selectionAtEnd = false;
backspace = (newChar == BACKSPACE);
delete = (newChar == DELETE);
paste = (newChar == PASTE);
if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste))) // A "key-typed" event
{
if (isValidCharacter(newChar)) {
if ((isPrintableCharacter(newChar)) || (backspace) || (delete) || (paste)) {
// Analyze the current contents of the field
currentText = getText();
currentLength = currentText.length();
selectionStart = getSelectionStart();
selectionEnd = getSelectionEnd();
insertionPoint = (selectionStart == selectionEnd);
selectionAtStart = (selectionStart == 0);
selectionAtEnd = (selectionEnd >= currentLength);
if (selectionEnd > currentLength) {
setSelectionEnd(currentLength);
}
// Generate new string
if (selectionStart > 0) // Create head of test string
{
endOfHead = selectionStart;
if (insertionPoint && backspace) {
endOfHead -= 1;
}
testString += currentText.substring(0, endOfHead);
}
if (!(backspace || delete || paste)) // Add the new character
{
testString += newChar;
}
if (paste) // Add the string from the clipboard
{
Transferable data = getToolkit().getSystemClipboard().getContents(this);
if (data != null) {
try {
String clipString = (String) data.getTransferData(DataFlavor.stringFlavor);
testString += clipString;
} catch (java.io.IOException ioe) {
// Do nothing
} catch (UnsupportedFlavorException ufe) {
// Do nothing
}
}
}
if (selectionEnd < currentLength) // Add the tail of the string
{
startOfTail = selectionEnd;
if (insertionPoint && delete) {
startOfTail += 1;
}
testString += currentText.substring(startOfTail);
}
}
if (testString.compareTo("") != 0) // Null string is OK
{
if (!(isValidString(testString))) {
e.consume();
}
}
} else {
e.consume();
}
}
super.processKeyEvent(e);
postProcessing();
}
/*******************************
* PROTECTED METHODS
*******************************/
/**
* Default constructor for this class. The initial text of the smart text field
* can be specified as well as the size (in characters) of the text field.
*
* @param text The initial string that is displayed in the text field.
* @param columns THe width of the text field in characters.
*/
protected SmartTextField(String text, int columns) {
super(text, columns);
}
/**
* Returns whether a character is valid for this text field. As the user types
* from the keyboard, this method is called to determine if the character is a
* valid character based in the restrictions of the subclass.
*
* @param aChar A character to perform the validity test with.
* @return True if the character is valid for this subclassed text field.
* False is the character is not valid.
*/
abstract protected boolean isValidCharacter(char aChar);
/**
* Returns whether a string is valid for this text field. As the user types from
* the keyboard, this method is called to determine if the new string in the
* text field is valid based upon the restriction of the subclass. This is done
* after the character has been determined to be valid since there can be
* restrictions placed on the text string as a whole, such a maximum length or
* date format.
*
* @param aString The string to perform the validity check with.
* @return True if the string is valid for this subclassed text field.
* False if the character is not valud.
*/
abstract protected boolean isValidString(String aString);
/**
* This method is used by the any subclass that need to complete any processing
* of the text string in the text field after all the requirement checks have
* been performed.
*/
abstract protected void postProcessing();
/*******************************
* PRIVATE METHODS
*******************************/
private boolean isPrintableCharacter(char inputChar) {
if ((inputChar >= ' ') && (inputChar <= '~')) {
return true;
}
return false;
}
}