Formatted TextField Example
/*
Core SWING Advanced Programming
By Kim Topley
ISBN: 0 13 083292 8
Publisher: Prentice Hall
*/
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.metal.MetalTextFieldUI;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.FieldView;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;
import javax.swing.text.Position;
import javax.swing.text.Segment;
import javax.swing.text.Utilities;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
public class FormattedTextFieldExample {
public static void main(String[] args) {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (Exception evt) {}
JFrame f = new JFrame("Formatted Text Field");
final FormattedTextField tf = new FormattedTextField();
FormattedTextField.FormatSpec formatSpec = new FormattedTextField.FormatSpec(
"(...)-...-....", "(***)-***-****");
tf.setFormatSpec(formatSpec);
f.getContentPane().add(tf, "Center");
f.getContentPane().add(new JLabel("Phone Number: ", JLabel.RIGHT),
"West");
tf.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
System.out.println("Field content is <" + tf.getText() + ">");
}
});
f.pack();
f.setVisible(true);
}
}
class FormattedTextField extends JTextField {
public FormattedTextField() {
this(null, null, 0, null);
}
public FormattedTextField(String text, FormatSpec spec) {
this(null, text, 0, spec);
}
public FormattedTextField(int columns, FormatSpec spec) {
this(null, null, columns, spec);
}
public FormattedTextField(String text, int columns, FormatSpec spec) {
this(null, text, columns, spec);
}
public FormattedTextField(Document doc, String text, int columns,
FormatSpec spec) {
super(doc, text, columns);
setFont(new Font("monospaced", Font.PLAIN, 14));
if (spec != null) {
setFormatSpec(spec);
}
}
public void updateUI() {
setUI(new FormattedTextFieldUI());
}
public FormatSpec getFormatSpec() {
return formatSpec;
}
public void setFormatSpec(FormattedTextField.FormatSpec formatSpec) {
FormatSpec oldFormatSpec = this.formatSpec;
// Do nothing if no change to the format specification
if (formatSpec.equals(oldFormatSpec) == false) {
this.formatSpec = formatSpec;
// Limit the input to the number of markers.
Document doc = getDocument();
if (doc instanceof BoundedPlainDocument) {
((BoundedPlainDocument) doc).setMaxLength(formatSpec
.getMarkerCount());
}
// Notify a change in the format spec
firePropertyChange(FORMAT_PROPERTY, oldFormatSpec, formatSpec);
}
}
// Use a model that bounds the input length
protected Document createDefaultModel() {
BoundedPlainDocument doc = new BoundedPlainDocument();
doc
.addInsertErrorListener(new BoundedPlainDocument.InsertErrorListener() {
public void insertFailed(BoundedPlainDocument doc,
int offset, String str, AttributeSet a) {
// Beep when the field is full
Toolkit.getDefaultToolkit().beep();
}
});
return doc;
}
public static class FormatSpec {
public FormatSpec(String format, String mask) {
this.format = format;
this.mask = mask;
this.formatSize = format.length();
if (formatSize != mask.length()) {
throw new IllegalArgumentException(
"Format and mask must be the same size");
}
for (int i = 0; i < formatSize; i++) {
if (mask.charAt(i) == MARKER_CHAR) {
markerCount++;
}
}
}
public String getFormat() {
return format;
}
public String getMask() {
return mask;
}
public int getFormatSize() {
return formatSize;
}
public int getMarkerCount() {
return markerCount;
}
public boolean equals(Object fmt) {
return fmt != null && (fmt instanceof FormatSpec)
&& ((FormatSpec) fmt).getFormat().equals(format)
&& ((FormatSpec) fmt).getMask().equals(mask);
}
public String toString() {
return "FormatSpec with format <" + format + ">, mask <" + mask
+ ">";
}
private String format;
private String mask;
private int formatSize;
private int markerCount;
public static final char MARKER_CHAR = '*';
}
protected FormatSpec formatSpec;
public static final String FORMAT_PROPERTY = "format";
}
class BoundedPlainDocument extends PlainDocument {
public BoundedPlainDocument() {
// Default constructor - must use setMaxLength later
this.maxLength = 0;
}
public BoundedPlainDocument(int maxLength) {
this.maxLength = maxLength;
}
public BoundedPlainDocument(AbstractDocument.Content content, int maxLength) {
super(content);
if (content.length() > maxLength) {
throw new IllegalArgumentException(
"Initial content larger than maximum size");
}
this.maxLength = maxLength;
}
public void setMaxLength(int maxLength) {
if (getLength() > maxLength) {
throw new IllegalArgumentException(
"Current content larger than new maximum size");
}
this.maxLength = maxLength;
}
public int getMaxLength() {
return maxLength;
}
public void insertString(int offset, String str, AttributeSet a)
throws BadLocationException {
if (str == null) {
return;
}
// Note: be careful here - the content always has a
// trailing newline, which should not be counted!
int capacity = maxLength + 1 - getContent().length();
if (capacity >= str.length()) {
// It all fits
super.insertString(offset, str, a);
} else {
// It doesn't all fit. Add as much as we can.
if (capacity > 0) {
super.insertString(offset, str.substring(0, capacity), a);
}
// Finally, signal an error.
if (errorListener != null) {
errorListener.insertFailed(this, offset, str, a);
}
}
}
public void addInsertErrorListener(InsertErrorListener l) {
if (errorListener == null) {
errorListener = l;
return;
}
throw new IllegalArgumentException(
"InsertErrorListener already registered");
}
public void removeInsertErrorListener(InsertErrorListener l) {
if (errorListener == l) {
errorListener = null;
}
}
public interface InsertErrorListener {
public abstract void insertFailed(BoundedPlainDocument doc, int offset,
String str, AttributeSet a);
}
protected InsertErrorListener errorListener; // Unicast listener
protected int maxLength;
}
class FormattedTextFieldUI extends MetalTextFieldUI implements
PropertyChangeListener {
public static ComponentUI createUI(JComponent c) {
return new FormattedTextFieldUI();
}
public FormattedTextFieldUI() {
super();
}
public void installUI(JComponent c) {
super.installUI(c);
if (c instanceof FormattedTextField) {
c.addPropertyChangeListener(this);
editor = (FormattedTextField) c;
formatSpec = editor.getFormatSpec();
}
}
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
c.removePropertyChangeListener(this);
}
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(FormattedTextField.FORMAT_PROPERTY)) {
// Install the new format specification
formatSpec = editor.getFormatSpec();
// Recreate the View hierarchy
modelChanged();
}
}
// ViewFactory method - creates a view
public View create(Element elem) {
return new FormattedFieldView(elem, formatSpec);
}
protected FormattedTextField.FormatSpec formatSpec;
protected FormattedTextField editor;
}
class FormattedFieldView extends FieldView {
public FormattedFieldView(Element elem,
FormattedTextField.FormatSpec formatSpec) {
super(elem);
this.contentBuff = new Segment();
this.measureBuff = new Segment();
this.workBuff = new Segment();
this.element = elem;
buildMapping(formatSpec); // Build the model -> view map
createContent(); // Update content string
}
// View methods start here
public float getPreferredSpan(int axis) {
int widthFormat;
int widthContent;
if (formatSize == 0 || axis == View.Y_AXIS) {
return super.getPreferredSpan(axis);
}
widthFormat = Utilities.getTabbedTextWidth(measureBuff,
getFontMetrics(), 0, this, 0);
widthContent = Utilities.getTabbedTextWidth(contentBuff,
getFontMetrics(), 0, this, 0);
return Math.max(widthFormat, widthContent);
}
public Shape modelToView(int pos, Shape a, Position.Bias b)
throws BadLocationException {
a = adjustAllocation(a);
Rectangle r = new Rectangle(a.getBounds());
FontMetrics fm = getFontMetrics();
r.height = fm.getHeight();
int oldCount = contentBuff.count;
if (pos < offsets.length) {
contentBuff.count = offsets[pos];
} else {
// Beyond the end: point to the location
// after the last model position.
contentBuff.count = offsets[offsets.length - 1] + 1;
}
int offset = Utilities.getTabbedTextWidth(contentBuff, metrics, 0,
this, element.getStartOffset());
contentBuff.count = oldCount;
r.x += offset;
r.width = 1;
return r;
}
public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {
a = adjustAllocation(a);
bias[0] = Position.Bias.Forward;
int x = (int) fx;
int y = (int) fy;
Rectangle r = a.getBounds();
int startOffset = element.getStartOffset();
int endOffset = element.getEndOffset();
if (y < r.y || x < r.x) {
return startOffset;
} else if (y > r.y + r.height || x > r.x + r.width) {
return endOffset - 1;
}
// The given position is within the bounds of the view.
int offset = Utilities.getTabbedTextOffset(contentBuff,
getFontMetrics(), r.x, x, this, startOffset);
// The offset includes characters not in the model,
// so get rid of them to return a true model offset.
for (int i = 0; i < offsets.length; i++) {
if (offset <= offsets[i]) {
offset = i;
break;
}
}
// Don't return an offset beyond the data
// actually in the model.
if (offset > endOffset - 1) {
offset = endOffset - 1;
}
return offset;
}
public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
super.insertUpdate(changes, adjustAllocation(a), f);
createContent(); // Update content string
}
public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
super.removeUpdate(changes, adjustAllocation(a), f);
createContent(); // Update content string
}
// End of View methods
// View drawing methods: overridden from PlainView
protected void drawLine(int line, Graphics g, int x, int y) {
// Set the colors
JTextComponent host = (JTextComponent) getContainer();
unselected = (host.isEnabled()) ? host.getForeground() : host
.getDisabledTextColor();
Caret c = host.getCaret();
selected = c.isSelectionVisible() ? host.getSelectedTextColor()
: unselected;
int p0 = element.getStartOffset();
int p1 = element.getEndOffset() - 1;
int sel0 = ((JTextComponent) getContainer()).getSelectionStart();
int sel1 = ((JTextComponent) getContainer()).getSelectionEnd();
try {
// If the element is empty or there is no selection
// in this view, just draw the whole thing in one go.
if (p0 == p1 || sel0 == sel1 || inView(p0, p1, sel0, sel1) == false) {
drawUnselectedText(g, x, y, 0, contentBuff.count);
return;
}
// There is a selection in this view. Draw up to three regions:
// (a) The unselected region before the selection.
// (b) The selected region.
// (c) The unselected region after the selection.
// First, map the selected region offsets to be relative
// to the start of the region and then map them to view
// offsets so that they take into account characters not
// present in the model.
int mappedSel0 = mapOffset(Math.max(sel0 - p0, 0));
int mappedSel1 = mapOffset(Math.min(sel1 - p0, p1 - p0));
if (mappedSel0 > 0) {
// Draw an initial unselected region
x = drawUnselectedText(g, x, y, 0, mappedSel0);
}
x = drawSelectedText(g, x, y, mappedSel0, mappedSel1);
if (mappedSel1 < contentBuff.count) {
drawUnselectedText(g, x, y, mappedSel1, contentBuff.count);
}
} catch (BadLocationException e) {
// Should not happen!
}
}
protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
throws BadLocationException {
g.setColor(unselected);
workBuff.array = contentBuff.array;
workBuff.offset = p0;
workBuff.count = p1 - p0;
return Utilities.drawTabbedText(workBuff, x, y, g, this, p0);
}
protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
throws BadLocationException {
workBuff.array = contentBuff.array;
workBuff.offset = p0;
workBuff.count = p1 - p0;
g.setColor(selected);
return Utilities.drawTabbedText(workBuff, x, y, g, this, p0);
}
// End of View drawing methods
// Build the model-to-view mapping
protected void buildMapping(FormattedTextField.FormatSpec formatSpec) {
formatSize = formatSpec != null ? formatSpec.getFormatSize() : 0;
if (formatSize != 0) {
// Save the format string as a character array
formatChars = formatSpec.getFormat().toCharArray();
// Allocate a buffer to store the formatted string
formattedContent = new char[formatSize];
contentBuff.offset = 0;
contentBuff.count = formatSize;
contentBuff.array = formattedContent;
// Keep the mask for computing
// the preferred horizontal span, but use
// a wide character for measurement
char[] maskChars = formatSpec.getMask().toCharArray();
measureBuff.offset = 0;
measureBuff.array = maskChars;
measureBuff.count = formatSize;
// Get the number of markers
markerCount = formatSpec.getMarkerCount();
// Allocate an array to hold the offsets
offsets = new int[markerCount];
// Create the offset array
markerCount = 0;
for (int i = 0; i < formatSize; i++) {
if (maskChars[i] == FormattedTextField.FormatSpec.MARKER_CHAR) {
offsets[markerCount++] = i;
// Replace marker with a wide character
// in the array used for measurement.
maskChars[i] = WIDE_CHARACTER;
}
}
}
}
// Use the document content and the format
// string to build the display content
protected void createContent() {
try {
Document doc = getDocument();
int startOffset = element.getStartOffset();
int endOffset = element.getEndOffset();
int length = endOffset - startOffset - 1;
// If there is no format, use the raw data.
if (formatSize != 0) {
// Get the document content
doc.getText(startOffset, length, workBuff);
// Initialize the output buffer with the
// format string.
System.arraycopy(formatChars, 0, formattedContent, 0,
formatSize);
// Insert the model content into
// the target string.
int count = Math.min(length, markerCount);
int firstOffset = workBuff.offset;
// Place the model data into the output array
for (int i = 0; i < count; i++) {
formattedContent[offsets[i]] = workBuff.array[i
+ firstOffset];
}
} else {
doc.getText(startOffset, length, contentBuff);
}
} catch (BadLocationException bl) {
contentBuff.count = 0;
}
}
// Map a document offset to a view offset.
protected int mapOffset(int pos) {
pos -= element.getStartOffset();
if (pos >= offsets.length) {
return contentBuff.count;
} else {
return offsets[pos];
}
}
// Determines whether the selection intersects
// a given range of model offsets.
protected boolean inView(int p0, int p1, int sel0, int sel1) {
if (sel0 >= p0 && sel0 < p1) {
return true;
}
if (sel0 < p0 && sel1 >= p0) {
return true;
}
return false;
}
protected char[] formattedContent; // The formatted content for display
protected char[] formatChars; // The format string as characters
protected Segment contentBuff; // Segment pointing to formatted content
protected Segment measureBuff; // Segment pointing to mask string
protected Segment workBuff; // Segment used for scratch purposes
protected Element element; // The mapped element
protected int[] offsets; // Model-to-view offsets
protected Color selected; // Selected text color
protected Color unselected; // Unselected text color
protected int formatSize; // Length of the formatting string
protected int markerCount; // Number of markers in the format
protected static final char WIDE_CHARACTER = 'm';
}
Related examples in the same category