001    package util.swing;
002    
003    import util.swing.InputFilter;
004    import java.awt.event.*;
005    import javax.swing.JTextField;
006    import javax.swing.text.*;
007    
008    
009    /**
010     * An input field that lets you easily set up input filters. 
011     * 
012     * <p>This class derives from JTextField and adds support for the {@link InputFilter}
013     * interface. This way you can validate input much more easily.</p> 
014     * 
015     * <p>The filters can react when the input content is changed and when the user exits
016     * the control. So, e.g. you can check while editing that it is an integer and when 
017     * leaving check for correct input bounds. Doing so already when typing is quite 
018     * problematic since not all number ranges can then be covered. </p> 
019     * 
020     * <p><b>Example</b> for using JFilterInput: This will allow only binary numbers for 
021     * input.
022     * <br/><br/>
023     * <tt> JFilterInput jfiBinary = new JFilterInput(new InputFilter() {<br/>
024     *      &nbsp;&nbsp;public boolean allowEditing(String value) {<br/> 
025     *      &nbsp;&nbsp;&nbsp;&nbsp;return !value.matches(".*[^01]+.*");<br/> 
026     *      &nbsp;&nbsp;}<br/> 
027     *      &nbsp;&nbsp;public String validExit(String value) {<br/> 
028     *      &nbsp;&nbsp;&nbsp;&nbsp;return value;<br/> &nbsp;&nbsp;}<br/> 
029     *      });</tt></p>
030     *      
031     * @author   Thomas Ryssel
032     * @version  3.2
033     * @since    3.2 2006-03-18
034     */
035    public class JFilterInput extends JTextField {
036            
037            /**
038             * ID for serialization.
039             */
040            private static final long serialVersionUID = -6448548193991908200L;
041    
042            /**
043             * The filter used for this input. If set to <code>null</code>, no filter
044             * will be applied.
045             */
046            private InputFilter m_ifFilter = null;
047            
048            /**
049             * Internal flag indicating that changes will be allowed without any
050             * conditions.
051             */
052            private boolean m_bForceAllow = false;
053            
054            /**
055             * Create a JFilterInput without a filter.
056             * 
057             * <p>This will then behave like a normal JTextField.
058             * </p>
059             */
060            public JFilterInput() {
061                    this("", JTextField.LEFT, null);
062            }
063            
064            /**
065             * Create a JFilterInput with the given filter.
066             * 
067             * @param filter The filter to apply on this.
068             */
069            public JFilterInput(InputFilter filter) {
070                    this("", JTextField.LEFT, filter);
071            }
072            
073            /**
074             * Create a JFilterInput with the given parameters.
075             * 
076             * @param text The default text.
077             * @param type The input type (text alignment).
078             * @param filter The filter to apply on this.
079             */
080            public JFilterInput(String text, int type, InputFilter filter) {
081                    super(text, type);
082                    m_ifFilter = filter;
083                    addFocusListener(new FocusListener() {
084                            public void focusGained(FocusEvent e) {
085                                    // does nothing at the current implementation.
086                            }
087    
088                            public void focusLost(FocusEvent e) {
089                                    if (m_ifFilter != null) {
090                                            try {
091                                                    boolean oldForceAllow = m_bForceAllow;
092                                                    m_bForceAllow = true;
093                                                    setText(m_ifFilter.validExit(getText(0, getDocument().getLength())));
094                                                    m_bForceAllow = oldForceAllow;
095                                            } catch (BadLocationException ble) { }
096                                    }
097                                    
098                            }
099                    });
100            }
101            
102            /**
103             * Set the InputFilter that should be used from now on.
104             * 
105             * <p><b>Note:</b> This will simply set the new filter reference. The input 
106             * already given will be in no way processed or validated.</p>
107             * 
108             * @param filter
109             */
110            public void setFilter(InputFilter filter) {
111                    m_ifFilter = filter;
112            }
113            
114            /**
115             * Create and return the input fields document.
116             */
117            protected Document createDefaultModel() {
118                    return new PlainDocument() {
119                            private static final long serialVersionUID = 3257289110602724153L;
120                            public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
121                                    String sText = getText(0, getLength());
122                                    if (sText == null) {
123                                            sText = "";
124                                            offs = 0;
125                                    }
126                                    sText = new StringBuffer(sText).insert(offs, str).toString();
127                                    
128                                    if (m_bForceAllow || m_ifFilter == null || m_ifFilter.allowEditing(sText)) {
129                                            super.insertString(offs, str, a);
130                                    }
131                            }
132                            
133                            public void remove(int offs, int len) throws BadLocationException {
134                                    String sText = getText(0, getLength());
135                                    
136                                    if (sText == null) {
137                                            return;
138                                    }
139                                    
140                                    sText = new StringBuffer(sText).delete(offs, offs + len).toString();
141                                    if (m_bForceAllow || m_ifFilter == null || m_ifFilter.allowEditing(sText)) {
142                                            super.remove(offs, len);
143                                    }
144                            }
145                    };
146            }
147            
148            /**
149             * Enable or disable the filter. If the filter is disabled, every kind of input
150             * will be accepted. This is mainly for internal use in cases it's useful.
151             * 
152             * @param  en  Whether (<code>true</code>) or not (<code>false</code>) to enable
153             *             the filter.
154             */
155            protected void setFilterEnabled(boolean en) {
156                    m_bForceAllow = !en;
157            }
158    }