Java tutorial
/* 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'; }