001    package org.springframework.richclient.form.binding.swing;
002    
003    import com.jgoodies.forms.layout.CellConstraints;
004    import com.jgoodies.forms.layout.FormLayout;
005    import org.springframework.binding.form.FormModel;
006    import org.springframework.richclient.components.BigDecimalTextField;
007    import org.springframework.richclient.components.UserInputListener;
008    import org.springframework.richclient.form.binding.support.CustomBinding;
009    
010    import javax.swing.*;
011    import java.math.BigDecimal;
012    
013    /**
014     * Binding to handle Numbers. Can be configured in different with shiftFactor/shiftScale and
015     * decorations.
016     * 
017     * @author jh
018     * @see org.springframework.richclient.form.binding.swing.NumberBinder
019     *
020     */
021    public class NumberBinding extends CustomBinding implements UserInputListener
022    {
023    
024        protected final BigDecimalTextField numberField;
025        
026        protected final boolean readOnly;
027        
028        private final String leftDecoration;
029        
030        private final String rightDecoration;
031        
032        private final BigDecimal shiftFactor;
033        
034        private int shiftScale;
035        
036        private boolean isSettingValue = false;
037    
038        /**
039         * Creates a NumberBinding.
040         * 
041         * @param requiredClass
042         *            Required class for this binding.
043         * @param component
044         *            The BigDecimalTextField to use.
045         * @param readOnly
046         *            Force readonly at all times.
047         * @param leftDecoration
048         *            Decorating label with string at left side.
049         * @param rightDecoration
050         *            Decorating label with string at right side.
051         * @param shiftFactor
052         *            Shifting factor to use when setting/getting number in
053         *            inputfield. Can eg be used to display percentages as ###.##
054         *            instead of #.####.
055         * @param shiftScale
056         *            Scale to set on BigDecimal.
057         * @param formModel
058         *            FormModel.
059         * @param formPropertyPath
060         *            PropertyPath.
061         */
062        public NumberBinding(Class requiredClass, BigDecimalTextField component, boolean readOnly,
063                String leftDecoration, String rightDecoration, BigDecimal shiftFactor, int shiftScale,
064                FormModel formModel, String formPropertyPath)
065        {
066            super(formModel, formPropertyPath, requiredClass);
067            this.numberField = component;
068            this.readOnly = readOnly;
069            this.leftDecoration = leftDecoration;
070            this.rightDecoration = rightDecoration;
071            this.shiftFactor = shiftFactor;
072            this.shiftScale = shiftScale;
073        }
074    
075        /**
076         * @inheritDoc
077         */
078        protected void valueModelChanged(Object newValue)
079        {
080            this.isSettingValue = true;
081            if ((this.shiftFactor != null) && (newValue != null)) // if shifting, class is BigDecimal
082                this.numberField.setValue(((BigDecimal) newValue).multiply(this.shiftFactor));
083            else
084                this.numberField.setValue((Number) newValue);
085            readOnlyChanged();
086            this.isSettingValue = false;
087        }
088    
089        /**
090         * @inheritDoc
091         */
092        protected JComponent doBindControl()
093        {
094            valueModelChanged(getValue());
095            this.numberField.addUserInputListener(this);
096            if ((this.leftDecoration == null) && (this.rightDecoration == null))
097                return this.numberField;
098    
099            return createPanelWithDecoration();
100        }
101    
102        /**
103         * @inheritDoc
104         */
105        public void update(JComponent component)
106        {
107            if (!this.isSettingValue && NumberBinding.this.numberField.isEditable())
108            {
109                Number value = NumberBinding.this.numberField.getValue();
110                if ((value != null) && (NumberBinding.this.shiftFactor != null))
111                    NumberBinding.this.controlValueChanged(((BigDecimal) value).divide(NumberBinding.this.shiftFactor,
112                            NumberBinding.this.shiftScale, BigDecimal.ROUND_UP));
113                else
114                    NumberBinding.this.controlValueChanged(value);
115            }
116        }
117    
118        /**
119         * Create a panel with (possibly) decorations on both sides. 
120         * 
121         * TODO This leaves one problem: when validating and eg coloring/adding overlay the
122         * panel is used instead of the inputfield. There could be an interface that
123         * returns the correct component to be handled while validating. 
124         * 
125         * @return a decorated component which contains the inputfield.
126         */
127        private JComponent createPanelWithDecoration()
128        {
129            StringBuffer columnLayout = new StringBuffer();
130            if (this.leftDecoration != null)
131                columnLayout.append("pref, 3dlu, ");
132            columnLayout.append("fill:pref:grow");
133            if (this.rightDecoration != null)
134                columnLayout.append(", 3dlu, pref");
135    
136            JPanel panel = new JPanel(new FormLayout(columnLayout.toString(),
137                    "fill:pref:grow")) {
138                public void requestFocus() {
139                    NumberBinding.this.numberField.requestFocus();
140                }
141                
142            };
143            CellConstraints cc = new CellConstraints();
144            int columnIndex = 1;
145            if (this.leftDecoration != null)
146            {
147                panel.add(new JLabel(this.leftDecoration), cc.xy(columnIndex, 1));
148                columnIndex += 2;
149            }
150            panel.add(this.numberField, cc.xy(columnIndex, 1));
151            if (this.rightDecoration != null)
152            {
153                columnIndex += 2;
154                panel.add(new JLabel(this.rightDecoration), cc.xy(columnIndex, 1));
155            }
156            return panel;
157        }
158    
159        /**
160         * @inheritDoc
161         */
162        protected void readOnlyChanged()
163        {
164            numberField.setEditable(isEnabled() && !this.readOnly && !isReadOnly());
165        }
166    
167        /**
168         * @inheritDoc
169         */
170        protected void enabledChanged()
171        {
172            this.numberField.setEnabled(isEnabled());
173            readOnlyChanged();
174        }
175    }