001    /*
002     * Copyright 2002-2004 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.binding.value.swing;
017    
018    import java.awt.event.FocusEvent;
019    import java.awt.event.FocusListener;
020    import java.beans.PropertyChangeEvent;
021    import java.beans.PropertyChangeListener;
022    import java.text.ParseException;
023    
024    import javax.swing.JFormattedTextField;
025    import javax.swing.event.DocumentEvent;
026    import javax.swing.event.DocumentListener;
027    
028    import org.springframework.binding.value.ValueModel;
029    import org.springframework.binding.value.support.AbstractValueModelAdapter;
030    
031    /**
032     * Sets the value of the value model associated with a formatted text field when the text field changes according to the
033     * value commit policy.
034     * 
035     * This setter will also update the formatted text field value when the underlying value model value changes.
036     * 
037     * @author Oliver Hutchison
038     * @author Keith Donald
039     */
040    public class FormattedTextFieldAdapter extends AbstractValueModelAdapter implements PropertyChangeListener,
041            DocumentListener, FocusListener {
042    
043        private final JFormattedTextField component;
044    
045        private boolean settingValue;
046    
047        private boolean ignoreValue;
048    
049        public FormattedTextFieldAdapter(JFormattedTextField component, ValueModel valueModel,
050                ValueCommitPolicy commitPolicy) {
051            super(valueModel);
052            this.component = component;
053            this.component.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT);
054            this.component.addPropertyChangeListener("value", this);
055            if (commitPolicy == ValueCommitPolicy.AS_YOU_TYPE) {
056                component.getDocument().addDocumentListener(this);
057            }
058            
059            // mathiasbr: register focus listener to avoid a race condition.
060            // If the formatted text field lost its focus the value will be replaced.
061            // This results into two events: the first one sets the value to null
062            // the second one sets the value to its value before it was set to null.
063            // If the focus is moved to the button which is bound to the validation
064            // and the field has a constraint (like required) the button will be disabled
065            // while it is pressed. Although the button will be enabled when the second 
066            // event with the previous value is set again but the button will lost its 
067            // armed and pressed state when it is disabled. 
068            // The result is that a user has to click the button twice 
069            // before the button fires the actionPerformed event
070            component.addFocusListener(this);
071            
072            initalizeAdaptedValue();
073        }
074    
075        protected void valueModelValueChanged(Object value) {
076            settingValue = true;
077            try {
078                component.setValue(value);
079            } finally {
080                settingValue = false;
081            }
082        }
083    
084        public void propertyChange(PropertyChangeEvent e) {
085            if (logger.isDebugEnabled()) {
086                Class valueClass = (e.getNewValue() != null ? e.getNewValue().getClass() : null);
087                logger.debug("Formatted text field property '" + e.getPropertyName() + "' changed; new value is '"
088                        + e.getNewValue() + "', valueClass=" + valueClass);
089            }
090            adaptedValueChanged(component.getValue());
091        }
092    
093        public void insertUpdate(DocumentEvent e) {
094            tryToCommitEdit();
095        }
096    
097        public void removeUpdate(DocumentEvent e) {
098            if (!ignoreValue)
099                tryToCommitEdit();
100            else
101                ignoreValue = false;
102        }
103    
104        public void changedUpdate(DocumentEvent e) {
105            tryToCommitEdit();
106        }
107    
108        private void tryToCommitEdit() {
109            if (!settingValue) {
110                try {
111                    component.commitEdit();
112                } catch (ParseException e) {
113                    // ignore
114                }
115            }
116        }
117    
118        public void focusGained(FocusEvent e) {
119            ignoreValue = false;
120        }
121    
122        public void focusLost(FocusEvent e) {
123            ignoreValue = true;
124        }
125    }