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 * <bean id="euroBinder" class="org.springframework.richclient.form.binding.swing.NumberBinder" lazy-init="true">
026 * <property name="format">
027 * <value>###,###,###,##0.00</value>
028 * </property>
029 * <property name="nrOfDecimals">
030 * <value type="int">2</value>
031 * </property>
032 * <property name="leftDecoration">
033 * <value>€</value>
034 * </property>
035 * </bean>
036 * </pre>
037 *
038 * <pre>
039 * <bean id="percentageBinder" class="org.springframework.richclient.form.binding.swing.NumberBinder" lazy-init="true">
040 * <property name="nrOfNonDecimals">
041 * <value type="int">3</value>
042 * </property>
043 * <property name="nrOfDecimals">
044 * <value type="int">4</value>
045 * </property>
046 * <property name="rightDecoration">
047 * <value>%</value>
048 * </property>
049 * <property name="shiftFactor">
050 * <value type="java.math.BigDecimal">100</value>
051 * </property>
052 * </bean>
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 }