001    package org.springframework.richclient.form.binding.swing;
002    
003    import java.math.BigDecimal;
004    import java.text.DecimalFormat;
005    import java.util.Map;
006    
007    import javax.swing.JComponent;
008    import javax.swing.SwingConstants;
009    
010    import org.springframework.binding.form.FormModel;
011    import org.springframework.richclient.form.binding.Binding;
012    import org.springframework.richclient.form.binding.support.AbstractBinder;
013    import org.springframework.richclient.components.BigDecimalTextField;
014    import org.springframework.util.Assert;
015    
016    /**
017     * <p>Binder for numeric fields. Constructs a {@see org.springframework.richclient.form.binding.swing.NumberBinding} which holds
018     * a special inputfield {@see org.springframework.richclient.swing.BigDecimalTextField}.</p> 
019     * 
020     * <p>This binder comes with a set of configuration properties which makes this easy reusable.</p>
021     * 
022     * <p>
023     * Examples:
024     * <pre>
025     * &lt;bean id="euroBinder" class="org.springframework.richclient.form.binding.swing.NumberBinder" lazy-init="true"&gt;
026     *    &lt;property name="format"&gt;
027     *     &lt;value&gt;###,###,###,##0.00&lt;/value&gt;
028     *   &lt;/property&gt;
029     *   &lt;property name="nrOfDecimals"&gt;
030     *     &lt;value type="int"&gt;2&lt;/value&gt;
031     *   &lt;/property&gt;
032     *   &lt;property name="leftDecoration"&gt;
033     *     &lt;value&gt;&#x20ac;&lt;/value&gt;
034     *   &lt;/property&gt;
035     * &lt;/bean&gt;
036     * </pre>
037     * 
038     * <pre>
039     * &lt;bean id="percentageBinder" class="org.springframework.richclient.form.binding.swing.NumberBinder" lazy-init="true"&gt;
040     *   &lt;property name="nrOfNonDecimals"&gt;
041     *     &lt;value type="int"&gt;3&lt;/value&gt;
042     *   &lt;/property&gt;
043     *   &lt;property name="nrOfDecimals"&gt;
044     *     &lt;value type="int"&gt;4&lt;/value&gt;
045     *   &lt;/property&gt;
046     *   &lt;property name="rightDecoration"&gt;
047     *     &lt;value&gt;%&lt;/value&gt;
048     *   &lt;/property&gt;
049     *   &lt;property name="shiftFactor"&gt;
050     *     &lt;value type="java.math.BigDecimal"&gt;100&lt;/value&gt;
051     *   &lt;/property&gt;
052     * &lt;/bean&gt;
053     * </pre>
054     * </p>
055     * 
056     * TODO it might be better to get the number of decimals/nonDecimals from the format 
057     * 
058     * @author jh
059     *
060     */
061    public class NumberBinder extends AbstractBinder
062    {
063    
064        protected boolean readOnly = false;
065        
066        protected String format = null;
067        
068        protected String unformat = null;
069        
070        protected int nrOfDecimals = 2;
071        
072        protected int nrOfNonDecimals = 10;
073        
074        protected boolean negativeSign = true;
075        
076        protected String leftDecoration = null;
077        
078        protected String rightDecoration = null;
079        
080        // actual displayed value is multiplied/divided inner value
081        protected BigDecimal shiftFactor = null;
082    
083        protected Integer scale = null;
084        
085        protected int alignment = SwingConstants.RIGHT;
086    
087        /**
088         * <p>Default constructor.</p>
089         * 
090         * <p>Sets BigDecimal as requiredSourceClass.</p>
091         */
092        public NumberBinder() {
093            super(BigDecimal.class);
094        }
095    
096        /**
097         * Constructor taking the requiredSourceClass for this binder.
098         * 
099         * @param requiredSourceClass
100         *            Required source class.
101         */
102        public NumberBinder(Class requiredSourceClass)
103        {
104            super(requiredSourceClass);
105        }
106    
107        /**
108         * Force this inputField to be readOnly.
109         * 
110         * @param readOnly  
111         */
112        public void setReadOnly(boolean readOnly)
113        {
114            this.readOnly = readOnly;
115        }
116    
117        /**
118         * Set a decoration to the left of the inputField.
119         * 
120         * @param leftDecoration
121         *            Decoration to be placed.
122         */
123        public void setLeftDecoration(String leftDecoration)
124        {
125            this.leftDecoration = leftDecoration;
126        }
127    
128        /**
129         * Set a decoration to the right of the inputField.
130         * 
131         * @param rightDecoration
132         *            Decoration to be placed.
133         */
134        public void setRightDecoration(String rightDecoration)
135        {
136            this.rightDecoration = rightDecoration;
137        }
138    
139        /**
140         * Format that will be used to show this number.
141         * 
142         * @param format
143         *            NumberFormat.
144         */
145        public void setFormat(String format)
146        {
147            this.format = format;
148        }
149    
150        /**
151         * <p>
152         * Format that will be used when user is editing the field.
153         * </p>
154         * 
155         * <p>
156         * eg. when inputField gets focus, all formatting can be disabled. If focus
157         * is shifted, number will be formatted.
158         * </p>
159         * 
160         * @param unformat
161         *            NumberFormat
162         */
163        public void setUnformat(String unformat)
164        {
165            this.unformat = unformat;
166        }
167    
168        /**
169         * Maximum number of decimals. 
170         * 
171         * @param nrOfDecimals
172         */
173        public void setNrOfDecimals(int nrOfDecimals)
174        {
175            this.nrOfDecimals = nrOfDecimals;
176        }
177    
178        /**
179         * Maximum number of non-decimals.
180         * 
181         * @param nrOfNonDecimals
182         */
183        public void setNrOfNonDecimals(int nrOfNonDecimals)
184        {
185            this.nrOfNonDecimals = nrOfNonDecimals;
186        }
187    
188        /**
189         * Allow negative numbers. Default is <code>true</code>.
190         * 
191         * @param negativeSign
192         *            True if negative numbers are used.
193         */
194        public void setNegativeSign(boolean negativeSign)
195        {
196            this.negativeSign = negativeSign;
197        }
198    
199        /**
200         * <p>
201         * BigDecimals can be shifted right/left when storing.
202         * </p>
203         * 
204         * <p>
205         * Eg. percentages may be shown as <code>###.##</code> and saved as
206         * <code>#.####</code>.
207         * </p>
208         * 
209         * @param shiftFactor
210         *            Factor to shift number when saved.
211         */
212        public void setShiftFactor(BigDecimal shiftFactor)
213        {
214            Assert.isTrue(getRequiredSourceClass() == BigDecimal.class);
215            // Only BigDecimal's can divide safely
216            this.shiftFactor = shiftFactor;
217        }
218    
219        /**
220         * Enforce a given scale for the result.
221         * 
222         * @param scale
223         *            The scale to set.
224         *            
225         * @see BigDecimal#setScale(int)
226         */
227        public void setScale(Integer scale) {
228            this.scale = scale;
229        }
230    
231        /**
232         * Sets the horizontal aligment of the BigDecimalTextField. Default is SwingConstants.RIGHT.
233         * 
234         * @param alignment
235         *            Horizontal alignment to set.
236         */
237        public void setAlignment(int alignment)
238        {
239            this.alignment = alignment;
240        }
241        
242        /**
243         * @inheritDoc
244         */
245        protected JComponent createControl(Map context) {
246            BigDecimalTextField component = null;
247            if (this.format == null) {
248                component = new BigDecimalTextField(this.nrOfNonDecimals,
249                        this.nrOfDecimals, negativeSign, getRequiredSourceClass());
250            }
251    
252            if (component == null && this.unformat == null) {
253                component = new BigDecimalTextField(this.nrOfNonDecimals,
254                        this.nrOfDecimals, negativeSign, getRequiredSourceClass(),
255                        new DecimalFormat(this.format));
256            }
257    
258            if (component == null) {
259                component = new BigDecimalTextField(this.nrOfNonDecimals,
260                        this.nrOfDecimals, negativeSign, getRequiredSourceClass(),
261                        new DecimalFormat(this.format), new DecimalFormat(this.unformat));
262            }
263    
264            if (scale != null) {
265                component.setScale(scale);
266            }
267            
268            component.setHorizontalAlignment(this.alignment);
269    
270            return component;
271        }
272    
273        /**
274         * @inheritDoc
275         */
276        protected Binding doBind(JComponent control, FormModel formModel,
277                String formPropertyPath, Map context) {
278            Assert.isTrue(control instanceof BigDecimalTextField,
279                    "Control must be an instance of BigDecimalTextField.");
280            return new NumberBinding(getRequiredSourceClass(), (BigDecimalTextField) control, readOnly,
281                    this.leftDecoration, this.rightDecoration, this.shiftFactor, this.nrOfDecimals
282                            + this.nrOfNonDecimals, formModel, formPropertyPath);
283        }
284    
285    }