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>
+ * <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
+{
+
+ 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