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 }