Mega Code Archive

 
Categories / Java / Swing JFC
 

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'; }