/** * Copyright (C) 2015 Valkyrie RCP * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.valkyriercp.form.binding.swing; import org.springframework.util.Assert; import org.valkyriercp.binding.form.FormModel; import org.valkyriercp.component.BigDecimalTextField; import org.valkyriercp.form.binding.Binding; import org.valkyriercp.form.binding.support.AbstractBinder; import javax.swing.*; import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.Map; /** * <p>Binder for numeric fields. Constructs a {@see org.springframework.richclient.form.binding.swing.NumberBinding} which holds * a special inputfield {@see org.springframework.richclient.swing.BigDecimalTextField}.</p> * * <p>This binder comes with a set of configuration properties which makes this easy reusable.</p> * * <p> * Examples: * <pre> * <bean id="euroBinder" class="org.springframework.richclient.form.binding.swing.NumberBinder" lazy-init="true"> * <property name="format"> * <value>###,###,###,##0.00</value> * </property> * <property name="nrOfDecimals"> * <value type="int">2</value> * </property> * <property name="leftDecoration"> * <value>€</value> * </property> * </bean> * </pre> * * <pre> * <bean id="percentageBinder" class="org.springframework.richclient.form.binding.swing.NumberBinder" lazy-init="true"> * <property name="nrOfNonDecimals"> * <value type="int">3</value> * </property> * <property name="nrOfDecimals"> * <value type="int">4</value> * </property> * <property name="rightDecoration"> * <value>%</value> * </property> * <property name="shiftFactor"> * <value type="java.math.BigDecimal">100</value> * </property> * </bean> * </pre> * </p> * * TODO it might be better to get the number of decimals/nonDecimals from the format * * @author jh * */ public class NumberBinder extends AbstractBinder { public static final String FORMAT_KEY = "format"; public static final String UNFORMAT_KEY = "unformat"; public static final String SCALE_KEY = "scale"; public static final String NR_OF_NON_DECIMALS_KEY = "nrOfNonDecimals"; public static final String NR_OF_DECIMALS_KEY = "nrOfDecimals"; public static final String NEGATIVE_SIGN_KEY = "negativeSign"; public static final String ALIGNMENT_KEY = "alignment"; public static final String READ_ONLY_KEY = "readOnly"; public static final String LEFT_DECORATION_KEY = "leftDecoration"; public static final String RIGHT_DECORATION_KEY = "rightDecoration"; public static final String SHIFT_FACTOR_KEY = "shiftFactor"; protected boolean readOnly = false; protected String format = null; protected String unformat = null; protected int nrOfDecimals = 2; protected int nrOfNonDecimals = 10; protected boolean negativeSign = true; protected String leftDecoration = null; protected String rightDecoration = null; // actual displayed value is multiplied/divided inner value protected BigDecimal shiftFactor = null; protected Integer scale = null; protected int alignment = SwingConstants.RIGHT; /** * <p>Default constructor.</p> * * <p>Sets BigDecimal as requiredSourceClass.</p> */ public NumberBinder() { this(BigDecimal.class); } /** * Constructor taking the requiredSourceClass for this binder. * * @param requiredSourceClass * Required source class. */ public NumberBinder(Class requiredSourceClass) { super(requiredSourceClass, new String[] {FORMAT_KEY, UNFORMAT_KEY, SCALE_KEY, NR_OF_DECIMALS_KEY, NR_OF_NON_DECIMALS_KEY, NEGATIVE_SIGN_KEY, ALIGNMENT_KEY, READ_ONLY_KEY, LEFT_DECORATION_KEY, RIGHT_DECORATION_KEY, SHIFT_FACTOR_KEY}); } /** * Force this inputField to be readOnly. * * @param readOnly */ public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } /** * Set a decoration to the left of the inputField. * * @param leftDecoration * Decoration to be placed. */ public void setLeftDecoration(String leftDecoration) { this.leftDecoration = leftDecoration; } /** * Set a decoration to the right of the inputField. * * @param rightDecoration * Decoration to be placed. */ public void setRightDecoration(String rightDecoration) { this.rightDecoration = rightDecoration; } /** * Format that will be used to show this number. * * @param format * NumberFormat. */ public void setFormat(String format) { this.format = format; } /** * <p> * Format that will be used when user is editing the field. * </p> * * <p> * eg. when inputField gets focus, all formatting can be disabled. If focus * is shifted, number will be formatted. * </p> * * @param unformat * NumberFormat */ public void setUnformat(String unformat) { this.unformat = unformat; } /** * Maximum number of decimals. * * @param nrOfDecimals */ public void setNrOfDecimals(int nrOfDecimals) { this.nrOfDecimals = nrOfDecimals; } /** * Maximum number of non-decimals. * * @param nrOfNonDecimals */ public void setNrOfNonDecimals(int nrOfNonDecimals) { this.nrOfNonDecimals = nrOfNonDecimals; } /** * Allow negative numbers. Default is <code>true</code>. * * @param negativeSign * True if negative numbers are used. */ public void setNegativeSign(boolean negativeSign) { this.negativeSign = negativeSign; } /** * <p> * BigDecimals can be shifted right/left when storing. * </p> * * <p> * Eg. percentages may be shown as <code>###.##</code> and saved as * <code>#.####</code>. * </p> * * @param shiftFactor * Factor to shift number when saved. */ public void setShiftFactor(BigDecimal shiftFactor) { Assert.isTrue(getRequiredSourceClass() == BigDecimal.class); // Only BigDecimal's can divide safely this.shiftFactor = shiftFactor; } /** * Enforce a given scale for the result. * * @param scale * The scale to set. * * @see BigDecimal#setScale(int) */ public void setScale(Integer scale) { this.scale = scale; } /** * Sets the horizontal aligment of the BigDecimalTextField. Default is SwingConstants.RIGHT. * * @param alignment * Horizontal alignment to set. */ public void setAlignment(int alignment) { this.alignment = alignment; } /** * @inheritDoc */ protected JComponent createControl(Map context) { BigDecimalTextField component = null; String format; String unformat; Integer scale; Integer nrOfNonDecimals; Integer nrOfDecimals; Boolean negativeSign; Integer alignment; if(context.containsKey(FORMAT_KEY)) { format = (String) context.get(FORMAT_KEY); } else { format = this.format; } if(context.containsKey(UNFORMAT_KEY)) { unformat = (String) context.get(UNFORMAT_KEY); } else { unformat = this.unformat; } if(context.containsKey(SCALE_KEY)) { scale = (Integer) context.get(SCALE_KEY); } else { scale = this.scale; } if(context.containsKey(NR_OF_NON_DECIMALS_KEY)) { nrOfNonDecimals = (Integer) context.get(NR_OF_NON_DECIMALS_KEY); } else { nrOfNonDecimals = this.nrOfNonDecimals; } if(context.containsKey(NR_OF_DECIMALS_KEY)) { nrOfDecimals = (Integer) context.get(NR_OF_DECIMALS_KEY); } else { nrOfDecimals = this.nrOfDecimals; } if(context.containsKey(NEGATIVE_SIGN_KEY)) { negativeSign = (Boolean) context.get(NEGATIVE_SIGN_KEY); } else { negativeSign = this.negativeSign; } if(context.containsKey(ALIGNMENT_KEY)) { alignment = (Integer) context.get(ALIGNMENT_KEY); } else { alignment = this.alignment; } if (format == null) { component = new BigDecimalTextField(nrOfNonDecimals, nrOfDecimals, negativeSign, getRequiredSourceClass()); } if (component == null && unformat == null) { component = new BigDecimalTextField(nrOfNonDecimals, nrOfDecimals, negativeSign, getRequiredSourceClass(), new DecimalFormat(format)); } if (component == null) { component = new BigDecimalTextField(nrOfNonDecimals, nrOfDecimals, negativeSign, getRequiredSourceClass(), new DecimalFormat(format), new DecimalFormat(unformat)); } if (scale != null) { component.setScale(scale); } component.setHorizontalAlignment(alignment); return component; } /** * @inheritDoc */ protected Binding doBind(JComponent control, FormModel formModel, String formPropertyPath, Map context) { Assert.isTrue(control instanceof BigDecimalTextField, "Control must be an instance of BigDecimalTextField."); Integer nrOfNonDecimals; Integer nrOfDecimals; Boolean readOnly; String leftDecoration; String rightDecoration; BigDecimal shiftFactor; if(context.containsKey(NR_OF_NON_DECIMALS_KEY)) { nrOfNonDecimals = (Integer) context.get(NR_OF_NON_DECIMALS_KEY); } else { nrOfNonDecimals = this.nrOfNonDecimals; } if(context.containsKey(NR_OF_DECIMALS_KEY)) { nrOfDecimals = (Integer) context.get(NR_OF_DECIMALS_KEY); } else { nrOfDecimals = this.nrOfDecimals; } if(context.containsKey(READ_ONLY_KEY)) { readOnly = (Boolean) context.get(READ_ONLY_KEY); } else { readOnly = this.readOnly; } if(context.containsKey(LEFT_DECORATION_KEY)) { leftDecoration = (String) context.get(LEFT_DECORATION_KEY); } else { leftDecoration = this.leftDecoration; } if(context.containsKey(RIGHT_DECORATION_KEY)) { rightDecoration = (String) context.get(RIGHT_DECORATION_KEY); } else { rightDecoration = this.rightDecoration; } if(context.containsKey(SHIFT_FACTOR_KEY)) { shiftFactor = (BigDecimal) context.get(SHIFT_FACTOR_KEY); } else { shiftFactor = this.shiftFactor; } return new NumberBinding(getRequiredSourceClass(), (BigDecimalTextField) control, readOnly, leftDecoration, rightDecoration, shiftFactor, nrOfDecimals + nrOfNonDecimals, formModel, formPropertyPath); } }