Revision: 1200
Author:   jhoskens
Date:     2006-06-14 00:16:12 -0700 (Wed, 14 Jun 2006)
ViewCVS:  http://svn.sourceforge.net/spring-rich-c/?rev=1200&view=rev

Log Message:
-----------
NumberBinding that can be reused. See javadoc in NumberBinder for possible 
configurations.

Added Paths:
-----------
    
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/form/binding/swing/NumberBinder.java
    
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/form/binding/swing/NumberBinding.java
    
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/swing/BigDecimalTextField.java
    
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/swing/UserInputListener.java
Added: 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/form/binding/swing/NumberBinder.java
===================================================================
--- 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/form/binding/swing/NumberBinder.java
                           (rev 0)
+++ 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/form/binding/swing/NumberBinder.java
   2006-06-14 07:16:12 UTC (rev 1200)
@@ -0,0 +1,283 @@
+package org.springframework.richclient.form.binding.swing;
+
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.util.Map;
+
+import javax.swing.JComponent;
+import javax.swing.SwingConstants;
+
+import org.springframework.binding.form.FormModel;
+import org.springframework.richclient.form.binding.Binding;
+import org.springframework.richclient.form.binding.support.AbstractBinder;
+import org.springframework.richclient.swing.BigDecimalTextField;
+import org.springframework.util.Assert;
+
+/**
+ * <p>Binder for numeric fields. Constructs a [EMAIL PROTECTED] 
org.springframework.richclient.form.binding.swing.NumberBinding} which holds
+ * a special inputfield [EMAIL PROTECTED] 
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>
+ * &lt;bean id="euroBinder" 
class="org.springframework.richclient.form.binding.swing.NumberBinder" 
lazy-init="true"&gt;
+ *    &lt;property name="format"&gt;
+ *     &lt;value&gt;###,###,###,##0.00&lt;/value&gt;
+ *   &lt;/property&gt;
+ *   &lt;property name="nrOfDecimals"&gt;
+ *     &lt;value type="int"&gt;2&lt;/value&gt;
+ *   &lt;/property&gt;
+ *   &lt;property name="leftDecoration"&gt;
+ *     &lt;value&gt;&#x20ac;&lt;/value&gt;
+ *   &lt;/property&gt;
+ * &lt;/bean&gt;
+ * </pre>
+ * 
+ * <pre>
+ * &lt;bean id="percentageBinder" 
class="org.springframework.richclient.form.binding.swing.NumberBinder" 
lazy-init="true"&gt;
+ *   &lt;property name="nrOfNonDecimals"&gt;
+ *     &lt;value type="int"&gt;3&lt;/value&gt;
+ *   &lt;/property&gt;
+ *   &lt;property name="nrOfDecimals"&gt;
+ *     &lt;value type="int"&gt;4&lt;/value&gt;
+ *   &lt;/property&gt;
+ *   &lt;property name="rightDecoration"&gt;
+ *     &lt;value&gt;%&lt;/value&gt;
+ *   &lt;/property&gt;
+ *   &lt;property name="shiftFactor"&gt;
+ *     &lt;value type="java.math.BigDecimal"&gt;100&lt;/value&gt;
+ *   &lt;/property&gt;
+ * &lt;/bean&gt;
+ * </pre>
+ * </p>
+ * 
+ * TODO it might be better to get the number of decimals/nonDecimals from the 
format 
+ * 
+ * @author jh
+ *
+ */
+public class NumberBinder extends AbstractBinder
+{
+
+    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() {
+        super(BigDecimal.class);
+    }
+
+    /**
+     * Constructor taking the requiredSourceClass for this binder.
+     * 
+     * @param requiredSourceClass
+     *            Required source class.
+     */
+    public NumberBinder(Class requiredSourceClass)
+    {
+        super(requiredSourceClass);
+    }
+
+    /**
+     * 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;
+        if (this.format == null) {
+            component = new BigDecimalTextField(this.nrOfNonDecimals,
+                    this.nrOfDecimals, negativeSign, getRequiredSourceClass());
+        }
+
+        if (component == null && this.unformat == null) {
+            component = new BigDecimalTextField(this.nrOfNonDecimals,
+                    this.nrOfDecimals, negativeSign, getRequiredSourceClass(),
+                    new DecimalFormat(this.format));
+        }
+
+        if (component == null) {
+            component = new BigDecimalTextField(this.nrOfNonDecimals,
+                    this.nrOfDecimals, negativeSign, getRequiredSourceClass(),
+                    new DecimalFormat(this.format), new 
DecimalFormat(this.unformat));
+        }
+
+        if (scale != null) {
+            component.setScale(scale);
+        }
+
+        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.");
+        return new NumberBinding(getRequiredSourceClass(), 
(BigDecimalTextField) control, readOnly,
+                this.leftDecoration, this.rightDecoration, this.shiftFactor, 
this.nrOfDecimals
+                        + this.nrOfNonDecimals, formModel, formPropertyPath);
+    }
+
+}

Added: 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/form/binding/swing/NumberBinding.java
===================================================================
--- 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/form/binding/swing/NumberBinding.java
                          (rev 0)
+++ 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/form/binding/swing/NumberBinding.java
  2006-06-14 07:16:12 UTC (rev 1200)
@@ -0,0 +1,181 @@
+package org.springframework.richclient.form.binding.swing;
+
+import java.math.BigDecimal;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+
+import org.springframework.binding.form.FormModel;
+import org.springframework.richclient.form.binding.support.CustomBinding;
+import org.springframework.richclient.swing.BigDecimalTextField;
+import org.springframework.richclient.swing.UserInputListener;
+
+import com.jgoodies.forms.layout.CellConstraints;
+import com.jgoodies.forms.layout.FormLayout;
+
+/**
+ * Binding to handle Numbers. Can be configured in different with 
shiftFactor/shiftScale and
+ * decorations.
+ * 
+ * @author jh
+ * @see org.springframework.richclient.form.binding.swing.NumberBinder
+ *
+ */
+public class NumberBinding extends CustomBinding implements UserInputListener
+{
+
+    protected final BigDecimalTextField numberField;
+    
+    protected final boolean readOnly;
+    
+    private final String leftDecoration;
+    
+    private final String rightDecoration;
+    
+    private final BigDecimal shiftFactor;
+    
+    private int shiftScale;
+    
+    private boolean isSettingValue = false;
+
+    /**
+     * Creates a NumberBinding.
+     * 
+     * @param requiredClass
+     *            Required class for this binding.
+     * @param component
+     *            The BigDecimalTextField to use.
+     * @param readOnly
+     *            Force readonly at all times.
+     * @param leftDecoration
+     *            Decorating label with string at left side.
+     * @param rightDecoration
+     *            Decorating label with string at right side.
+     * @param shiftFactor
+     *            Shifting factor to use when setting/getting number in
+     *            inputfield. Can eg be used to display percentages as ###.##
+     *            instead of #.####.
+     * @param shiftScale
+     *            Scale to set on BigDecimal.
+     * @param formModel
+     *            FormModel.
+     * @param formPropertyPath
+     *            PropertyPath.
+     */
+    public NumberBinding(Class requiredClass, BigDecimalTextField component, 
boolean readOnly,
+            String leftDecoration, String rightDecoration, BigDecimal 
shiftFactor, int shiftScale,
+            FormModel formModel, String formPropertyPath)
+    {
+        super(formModel, formPropertyPath, requiredClass);
+        this.numberField = component;
+        this.numberField.setHorizontalAlignment(SwingConstants.RIGHT);
+        this.readOnly = readOnly;
+        this.leftDecoration = leftDecoration;
+        this.rightDecoration = rightDecoration;
+        this.shiftFactor = shiftFactor;
+        this.shiftScale = shiftScale;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    protected void valueModelChanged(Object newValue)
+    {
+        this.isSettingValue = true;
+        if ((this.shiftFactor != null) && (newValue != null)) // if shifting, 
class is BigDecimal
+            this.numberField.setValue(((BigDecimal) 
newValue).multiply(this.shiftFactor));
+        else
+            this.numberField.setValue((Number) newValue);
+        readOnlyChanged();
+        this.isSettingValue = false;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    protected JComponent doBindControl()
+    {
+        valueModelChanged(getValue());
+        this.numberField.addUserInputListener(this);
+        if ((this.leftDecoration == null) && (this.rightDecoration == null))
+            return this.numberField;
+
+        return createPanelWithDecoration();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void update(JComponent component)
+    {
+        if (!this.isSettingValue && 
NumberBinding.this.numberField.isEditable())
+        {
+            Number value = NumberBinding.this.numberField.getValue();
+            if ((value != null) && (NumberBinding.this.shiftFactor != null))
+                NumberBinding.this.controlValueChanged(((BigDecimal) 
value).divide(NumberBinding.this.shiftFactor,
+                        NumberBinding.this.shiftScale, BigDecimal.ROUND_UP));
+            else
+                NumberBinding.this.controlValueChanged(value);
+        }
+    }
+
+    /**
+     * Create a panel with (possibly) decorations on both sides. 
+     * 
+     * TODO This leaves one problem: when validating and eg coloring/adding 
overlay the
+     * panel is used instead of the inputfield. There could be an interface 
that
+     * returns the correct component to be handled while validating. 
+     * 
+     * @return a decorated component which contains the inputfield.
+     */
+    private JComponent createPanelWithDecoration()
+    {
+        StringBuffer columnLayout = new StringBuffer();
+        if (this.leftDecoration != null)
+            columnLayout.append("pref, 3dlu, ");
+        columnLayout.append("fill:pref:grow");
+        if (this.rightDecoration != null)
+            columnLayout.append(", 3dlu, pref");
+
+        JPanel panel = new JPanel(new FormLayout(columnLayout.toString(),
+                "fill:pref:grow")) {
+            public void requestFocus() {
+                NumberBinding.this.numberField.requestFocus();
+            }
+            
+        };
+        CellConstraints cc = new CellConstraints();
+        int columnIndex = 1;
+        if (this.leftDecoration != null)
+        {
+            panel.add(new JLabel(this.leftDecoration), cc.xy(columnIndex, 1));
+            columnIndex += 2;
+        }
+        panel.add(this.numberField, cc.xy(columnIndex, 1));
+        if (this.rightDecoration != null)
+        {
+            columnIndex += 2;
+            panel.add(new JLabel(this.rightDecoration), cc.xy(columnIndex, 1));
+        }
+        return panel;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    protected void readOnlyChanged()
+    {
+        numberField.setEditable(isEnabled() && !this.readOnly && 
!isReadOnly());
+    }
+
+    /**
+     * @inheritDoc
+     */
+    protected void enabledChanged()
+    {
+        this.numberField.setEnabled(isEnabled());
+        readOnlyChanged();
+    }
+}

Added: 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/swing/BigDecimalTextField.java
===================================================================
--- 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/swing/BigDecimalTextField.java
                         (rev 0)
+++ 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/swing/BigDecimalTextField.java
 2006-06-14 07:16:12 UTC (rev 1200)
@@ -0,0 +1,504 @@
+package org.springframework.richclient.swing;
+
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.JTextField;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.PlainDocument;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.util.Assert;
+
+/**
+ * <p>
+ * This class can have different "read" and "write" formats. When showing the
+ * number the "read" format will be used. If the user enters the inputfield
+ * (gains focus), the "write" format will be used.
+ * </p>
+ * 
+ * <p>
+ * A maximum of decimals/non-decimals can be specified so no more numbers can 
be
+ * input than strictly defined.
+ * </p>
+ * 
+ * <p>
+ * A boolean can be specified to allow only positive numbers or positive and
+ * negative numbers. Switching between positive and negative can be done by
+ * using the +/- buttons anywhere in the inputfield.
+ * </p>
+ * 
+ * TODO There's a third option: only negative numbers, this should be 
configurable as well.
+ * 
+ * @author jh
+ * 
+ */
+public class BigDecimalTextField extends JTextField {
+       
+       private static final long serialVersionUID = -601376040393562990L;
+
+    Log log = LogFactory.getLog(BigDecimalTextField.class);
+    
+    public static final NumberFormat DEFAULT_FORMAT = new 
DecimalFormat("###,###,###,##0.######");
+    
+    public static final NumberFormat DEFAULT_UNFORMAT = new 
DecimalFormat("#0.#######");
+    
+    public static final DecimalFormatSymbols symbols = new 
DecimalFormatSymbols();
+
+    private Class numberClass = null;
+    
+    private final NumberFormat format;
+    
+    private final NumberFormat unformat;
+
+    private Integer scale;
+
+    private List listeners;
+    
+    private boolean internallySettingText = false;
+    
+    /**
+     * Default constructor.
+     */
+    public BigDecimalTextField()
+    {
+        this(2, 4, true);
+    }
+    
+    /**
+     * @see #BigDecimalTextField(int, int, boolean, Class, NumberFormat, 
NumberFormat)
+     */
+    public BigDecimalTextField(int nrOfNonDecimals, int nrOfDecimals, boolean 
negativeSign)
+    {
+        this(nrOfNonDecimals, nrOfDecimals, negativeSign, BigDecimal.class);
+    }
+
+    /**
+     * @see #BigDecimalTextField(int, int, boolean, Class, NumberFormat, 
NumberFormat)
+     */
+    public BigDecimalTextField(int nrOfNonDecimals, int nrOfDecimals, boolean 
negativeSign, Class numberClass)
+    {
+        this(nrOfNonDecimals, nrOfDecimals, negativeSign, numberClass, 
DEFAULT_FORMAT);
+    }
+    
+    /**
+     * @see #BigDecimalTextField(int, int, boolean, Class, NumberFormat, 
NumberFormat)
+     */
+    public BigDecimalTextField(int nrOfNonDecimals, int nrOfDecimals, boolean 
negativeSign, Class numberClass, NumberFormat format)
+    {
+        this(nrOfNonDecimals, nrOfDecimals, negativeSign, numberClass, format, 
DEFAULT_UNFORMAT);
+    }
+    
+    /**
+     * @param nrOfNonDecimals
+     *            Number of non-decimals.
+     * @param nrOfDecimals
+     *            Number of decimals.
+     * @param negativeSign
+     *            Negative numbers allowed.
+     * @param numberClass
+     *            Class type (default BigDecimal).
+     * @param format
+     *            The "read"-format.
+     * @param unformat
+     *            The "edit"-format.
+     */
+    public BigDecimalTextField(int nrOfNonDecimals, int nrOfDecimals, boolean 
negativeSign, Class numberClass, NumberFormat format, NumberFormat unformat)
+    {
+        super();
+        Assert.notNull(format);
+        Assert.notNull(unformat);
+        this.format = format;
+        setBigDecimalFormat(format, numberClass);
+        this.unformat = unformat;
+        setBigDecimalFormat(unformat, numberClass);
+        this.numberClass = numberClass;
+        setDocument(new BigDecimalDocument(nrOfNonDecimals, nrOfDecimals, 
negativeSign));
+        addFocusListener(new FormatFocusListener());
+    }
+
+    /**
+     * When parsing a number, BigDecimalFormat can return numbers different 
than BigDecimal.
+     * 
+     * @param format
+     * @param numberClass
+     * 
+     * @see DecimalFormat#setParseBigDecimal(boolean)
+     */
+    private static final void setBigDecimalFormat(NumberFormat format, Class 
numberClass)
+    {
+        if (format instanceof DecimalFormat && (numberClass == 
BigDecimal.class))
+        {
+            ((DecimalFormat)format).setParseBigDecimal(true);
+        }
+    }
+    
+    /**
+     * Add a UserInputListener.
+     * 
+     * @param listener
+     *            UserInputListener.
+     *            
+     * @see UserInputListener
+     */
+    public void addUserInputListener(UserInputListener listener) {
+        if (this.listeners == null)
+            this.listeners = new ArrayList();
+        this.listeners.add(listener);
+    }
+    
+    /**
+     * Remove a UserInputListener.
+     * 
+     * @param listener
+     *            UserInputListener.
+     *            
+     * @see UserInputListener
+     */
+    public void removeUserInputListener(UserInputListener listener) {
+        if (listeners != null) {
+           this.listeners.remove(listener);
+           }
+    }
+    
+    /**
+     * Fire an event to all UserInputListeners.
+     */
+    private void fireUserInputChange()
+    {
+        if (!internallySettingText && (this.listeners != null))
+        {
+            for (Iterator it = this.listeners.iterator(); it.hasNext();)
+            {
+                UserInputListener userInputListener = (UserInputListener) 
it.next();
+                userInputListener.update(this);
+            }
+        }
+    }
+    
+    /**
+     * Parses a number from the inputField and will adjust it's class if 
needed.
+     * 
+     * @return Number the Parsed number.
+     */
+    public Number getValue() {
+        if ((getText() == null) || "".equals(getText().trim()))
+            return null;
+        try {
+            Number n = format.parse(getText());
+            if (n.getClass() == this.numberClass)
+                return n;
+            else if (this.numberClass == BigDecimal.class) 
+            {
+                BigDecimal bd = new 
BigDecimal(Double.toString(n.doubleValue()));
+                if (scale != null) {
+                    bd.setScale(scale.intValue(), BigDecimal.ROUND_HALF_UP);
+                }
+                return bd;
+            } 
+            else if (this.numberClass == Double.class)
+                return new Double(n.doubleValue());
+            else if (this.numberClass == Float.class)
+                return new Float(n.floatValue());
+            else if (this.numberClass == BigInteger.class)
+                return new BigInteger(Integer.toString(n.intValue()));
+            else if (this.numberClass == Long.class)
+                return new Long(n.longValue());
+            else if (this.numberClass == Integer.class)
+                return new Integer(n.intValue());
+            else if (this.numberClass == Short.class)
+                return new Short(n.shortValue());
+            else if (this.numberClass == Byte.class)
+                return new Byte(n.byteValue());
+            return null;
+        } catch (Exception pe) {
+            log.error("Error:  " + getText() + " is not a number.", pe);
+            return null; 
+        }
+    }
+
+    /**
+     * Format the number and show it.
+     * 
+     * @param number
+     *            Number to set.
+     */
+    public void setValue(Number number) {
+        String txt = null;
+        if (number != null) {
+            txt = this.format.format(number.doubleValue());
+        }
+        setText(txt);
+    }
+
+    /**
+     * Set text internally: will change text but not fire any event.
+     * 
+     * @param s
+     *            Text to set.
+     */
+    private void setTextInternally(String s) {
+        internallySettingText = true;
+        setText(s);
+        internallySettingText = false;
+    }
+
+    /**
+     * <p>
+     * When inputField gets focus, the contents will switch to "edit"-format
+     * (=unformat). In most cases a format without all decorations, just the
+     * number. In addition a selectAll() will be done.
+     * </p>
+     * 
+     * TODO check if selectAll() is appropriate in all cases.
+     * 
+     * <p>
+     * When inputField loses focus, the contents will switch to "read"-format
+     * (=format). This will probably contain some decorations.
+     * </p>
+     */
+    class FormatFocusListener implements FocusListener {
+        /**
+         * Focus gained: "edit"-format and selectAll.
+         */
+        public void focusGained(FocusEvent e) {
+            String s = getText();
+            setTextInternally(format(unformat, format, s));
+            selectAll();
+        }
+
+        /**
+         * Focus lost: "read"-format.
+         */
+        public void focusLost(FocusEvent e) {
+            String s = getText();
+            setTextInternally(format(format, unformat, s));
+        }
+        
+        /**
+         * Format a string.
+         * 
+         * @param toFormat
+         *            Change to this format.
+         * @param fromFormat
+         *            Current format to be changed.
+         * @param s
+         *            String to be reformatted.
+         * @return String which holds the number in the new format.
+         */
+        private String format(NumberFormat toFormat, NumberFormat fromFormat, 
String s)
+        {
+            if (!"".equals(s))
+            {
+                try
+                {
+                    return toFormat.format(fromFormat.parse(s));
+                }
+                catch (ParseException pe)
+                {
+                    log.error("Fout: De ingevulde waarde " + getText() + " is 
geen nummer.", pe);
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Specific document that allows only input of numbers, decimal separator
+     * (or alternative) and sign. Maximum number of decimals/non-decimals will
+     * be respected at all times. Signing can be changed anywhere in the
+     * inputField by simply clicking +/-. Decimal separator input can be done
+     * with alternative character to allow both comma and point.
+     * 
+     * @author jh
+     */
+    class BigDecimalDocument extends PlainDocument
+    {
+        private final int nrOfNonDecimals;
+        
+        private final int nrOfDecimals;
+        
+        private final boolean negativeSign;
+        
+        private final char decimalSeparator = symbols.getDecimalSeparator();
+        
+        private final char alternativeSeparator;
+
+        /**
+         * @see #BigDecimalDocument(int, int, boolean, char)
+         */
+        public BigDecimalDocument() {
+            this(10, 2, true);
+        }
+
+        /**
+         * @see #BigDecimalDocument(int, int, boolean, char)
+         */
+        public BigDecimalDocument(int nrOfNonDecimals, int nrOfDecimals, 
boolean negativeSign)
+        {
+            this(nrOfNonDecimals, nrOfDecimals, negativeSign, 
symbols.getGroupingSeparator());
+        }
+
+        /**
+         * Constructor with several configurations. Alternative separator can 
be
+         * given in order to make input easier. Eg. Comma and point can be used
+         * for decimal separation.
+         * 
+         * @param nrOfNonDecimals
+         *            Maximum number of non-decimals.
+         * @param nrOfDecimals
+         *            Maximum number of decimals.
+         * @param negativeSign
+         *            Negative sign allowed.
+         * @param alternativeSeparator
+         *            Alternative separator.
+         */
+        public BigDecimalDocument(int nrOfNonDecimals, int nrOfDecimals,
+                boolean negativeSign, char alternativeSeparator) {
+            this.nrOfNonDecimals = nrOfNonDecimals;
+            this.nrOfDecimals = nrOfDecimals;
+            this.negativeSign = negativeSign;
+            this.alternativeSeparator = alternativeSeparator;
+        }
+
+        /**
+         * Handles string insertion, checks several things like number of 
non-decimals/decimals/sign...
+         * 
+         * @inheritDoc
+         */
+        public void insertString(int offset, String str, AttributeSet a)
+                throws BadLocationException
+        {
+            // first doing the single keys, then review what can be used for 
cut/paste actions
+            if ("-".equals(str))
+            {
+                if (this.negativeSign) // set - or flip to + if it's already 
there
+                {
+                    if((this.getLength() == 0) || !this.getText(0, 
1).equals("-"))
+                        super.insertString(0, str, a);
+                    else if (!(this.getLength() == 0) && this.getText(0, 
1).equals("-"))
+                        super.remove(0,1);
+                    fireUserInputChange();
+                }
+                return;
+            }
+            else if ("+".equals(str))
+            {
+                if (this.negativeSign && (!(this.getLength() == 0) && 
this.getText(0, 1).equals("-")))
+                {
+                    super.remove(0, 1);
+                    fireUserInputChange();
+                }
+                return;
+            } 
+            // check decimal signs
+            else if((str.length() == 1) && ((this.alternativeSeparator == 
str.charAt(0)) || (this.decimalSeparator == str.charAt(0))) )
+            {
+                if ((nrOfDecimals > 0) && (nrOfDecimals >= (getLength() - 
offset)) && (getText(0, getLength()).indexOf(this.decimalSeparator) == -1))
+                {
+                    super.insertString(offset, 
Character.toString(this.decimalSeparator), a);
+                    fireUserInputChange();
+                }
+                return;
+            }
+            String s = getText(0, offset) + str;
+            if (offset < getLength()) {
+                s += getText(offset, getLength() - offset);
+            }
+            
+            boolean isNegative = s.startsWith("-");
+            char[] sarr = isNegative ? s.substring(1).toCharArray() : s
+                    .toCharArray();
+            int sep = -1;
+            int numberLength = 0; // count numbers, no special characters
+            for (int i = 0; i < sarr.length; i++) {
+                if (sarr[i] == this.decimalSeparator) 
+                {
+                    if (sep != -1) 
+                    {// double decimalseparator??
+                        log.warn("Error while inserting string: " + s + 
"[pos=" + i + "]" + " Double decimalseparator?");
+                        return;
+                    }
+                    sep = i;
+                    if (numberLength > this.nrOfNonDecimals)
+                    {// too many digits left of decimal separator
+                        log.warn("Error while inserting string: " + s + 
"[pos=" + i + "]" + " Too many non decimals? [" + this.nrOfNonDecimals + "]");
+                        return; 
+                    }
+                    else if ((sarr.length - sep - 1) > this.nrOfDecimals)
+                    {// too many digits right of decimal separator
+                        log.warn("Error while inserting string: " + s + 
"[pos=" + i + "]" + " Too many decimals? [" + this.nrOfDecimals + "]");
+                        return; 
+                    }
+                } 
+                else if (sarr[i] == symbols.getGroupingSeparator()) 
+                {
+                    // ignore character
+                } 
+                else if (!Character.isDigit(sarr[i]))
+                {// non digit, no grouping/decimal separator not allowed
+                    log.warn("Error while inserting string: " + s + "[pos=" + 
i + "]" + " String contains character that is no digit or separator?");
+                    return;
+                }
+                else
+                    ++numberLength;
+            }
+            if ((sep == -1) && (numberLength > this.nrOfNonDecimals))
+            {// no separator, number too big
+                log.warn("Error while inserting string: " + s + " Too many non 
decimals? [" + this.nrOfNonDecimals + "]");
+                return; 
+            }
+            super.insertString(offset, str, a);
+            fireUserInputChange();
+        }
+        
+        /**
+         * Will trigger the UserInputListeners once after removing.
+         * 
+         * @inheritDoc
+         */
+        public void remove(int offs, int len) throws BadLocationException {
+            super.remove(offs, len);
+            fireUserInputChange();
+        }
+
+        /**
+         * Will trigger the UserInputListeners once after replacing.
+         * 
+         * @inheritDoc
+         */
+        public void replace(int offset, int length, String text,
+                AttributeSet attrs) throws BadLocationException {
+            boolean oldInternallySettingText = internallySettingText;
+            internallySettingText = true;
+            super.replace(offset, length, text, attrs);
+            internallySettingText = oldInternallySettingText;
+            fireUserInputChange();
+        }
+    }
+
+    /**
+     * @return Returns the scale.
+     */
+    public Integer getScale() {
+        return scale;
+}
+    /**
+     * @param scale
+     *            The scale to set.
+     */
+    public void setScale(Integer scale) {
+        this.scale = scale;
+    }
+}

Added: 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/swing/UserInputListener.java
===================================================================
--- 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/swing/UserInputListener.java
                           (rev 0)
+++ 
trunk/spring-richclient/sandbox/src/main/java/org/springframework/richclient/swing/UserInputListener.java
   2006-06-14 07:16:12 UTC (rev 1200)
@@ -0,0 +1,12 @@
+package org.springframework.richclient.swing;
+
+import javax.swing.JComponent;
+
+/**
+ * Separate listener because when format is changed during focus lost/gained,
+ * ordinary DocumentListener fires events. This listener can be used to listen
+ * to userInputEvents only.
+ */
+public interface UserInputListener {
+    public void update(JComponent source);
+}
\ No newline at end of file


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.



_______________________________________________
spring-rich-c-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/spring-rich-c-cvs

Reply via email to