--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/swing/text/NumberFormatter.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,505 @@
+/*
+ * Copyright 2000-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text;
+
+import java.lang.reflect.*;
+import java.text.*;
+import java.util.*;
+import javax.swing.text.*;
+
+/**
+ * <code>NumberFormatter</code> subclasses <code>InternationalFormatter</code>
+ * adding special behavior for numbers. Among the specializations are
+ * (these are only used if the <code>NumberFormatter</code> does not display
+ * invalid nubers, eg <code>setAllowsInvalid(false)</code>):
+ * <ul>
+ * <li>Pressing +/- (- is determined from the
+ * <code>DecimalFormatSymbols</code> associated with the
+ * <code>DecimalFormat</code>) in any field but the exponent
+ * field will attempt to change the sign of the number to
+ * positive/negative.
+ * <li>Pressing +/- (- is determined from the
+ * <code>DecimalFormatSymbols</code> associated with the
+ * <code>DecimalFormat</code>) in the exponent field will
+ * attemp to change the sign of the exponent to positive/negative.
+ * </ul>
+ * <p>
+ * If you are displaying scientific numbers, you may wish to turn on
+ * overwrite mode, <code>setOverwriteMode(true)</code>. For example:
+ * <pre>
+ * DecimalFormat decimalFormat = new DecimalFormat("0.000E0");
+ * NumberFormatter textFormatter = new NumberFormatter(decimalFormat);
+ * textFormatter.setOverwriteMode(true);
+ * textFormatter.setAllowsInvalid(false);
+ * </pre>
+ * <p>
+ * If you are going to allow the user to enter decimal
+ * values, you should either force the DecimalFormat to contain at least
+ * one decimal (<code>#.0###</code>), or allow the value to be invalid
+ * <code>setAllowsInvalid(true)</code>. Otherwise users may not be able to
+ * input decimal values.
+ * <p>
+ * <code>NumberFormatter</code> provides slightly different behavior to
+ * <code>stringToValue</code> than that of its superclass. If you have
+ * specified a Class for values, {@link #setValueClass}, that is one of
+ * of <code>Integer</code>, <code>Long</code>, <code>Float</code>,
+ * <code>Double</code>, <code>Byte</code> or <code>Short</code> and
+ * the Format's <code>parseObject</code> returns an instance of
+ * <code>Number</code>, the corresponding instance of the value class
+ * will be created using the constructor appropriate for the primitive
+ * type the value class represents. For example:
+ * <code>setValueClass(Integer.class)</code> will cause the resulting
+ * value to be created via
+ * <code>new Integer(((Number)formatter.parseObject(string)).intValue())</code>.
+ * This is typically useful if you
+ * wish to set a min/max value as the various <code>Number</code>
+ * implementations are generally not comparable to each other. This is also
+ * useful if for some reason you need a specific <code>Number</code>
+ * implementation for your values.
+ * <p>
+ * <strong>Warning:</strong>
+ * Serialized objects of this class will not be compatible with
+ * future Swing releases. The current serialization support is
+ * appropriate for short term storage or RMI between applications running
+ * the same version of Swing. As of 1.4, support for long term storage
+ * of all JavaBeans<sup><font size="-2">TM</font></sup>
+ * has been added to the <code>java.beans</code> package.
+ * Please see {@link java.beans.XMLEncoder}.
+ *
+ * @since 1.4
+ */
+public class NumberFormatter extends InternationalFormatter {
+ /** The special characters from the Format instance. */
+ private String specialChars;
+
+ /**
+ * Creates a <code>NumberFormatter</code> with the a default
+ * <code>NumberFormat</code> instance obtained from
+ * <code>NumberFormat.getNumberInstance()</code>.
+ */
+ public NumberFormatter() {
+ this(NumberFormat.getNumberInstance());
+ }
+
+ /**
+ * Creates a NumberFormatter with the specified Format instance.
+ *
+ * @param format Format used to dictate legal values
+ */
+ public NumberFormatter(NumberFormat format) {
+ super(format);
+ setFormat(format);
+ setAllowsInvalid(true);
+ setCommitsOnValidEdit(false);
+ setOverwriteMode(false);
+ }
+
+ /**
+ * Sets the format that dictates the legal values that can be edited
+ * and displayed.
+ * <p>
+ * If you have used the nullary constructor the value of this property
+ * will be determined for the current locale by way of the
+ * <code>NumberFormat.getNumberInstance()</code> method.
+ *
+ * @param format NumberFormat instance used to dictate legal values
+ */
+ public void setFormat(Format format) {
+ super.setFormat(format);
+
+ DecimalFormatSymbols dfs = getDecimalFormatSymbols();
+
+ if (dfs != null) {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(dfs.getCurrencySymbol());
+ sb.append(dfs.getDecimalSeparator());
+ sb.append(dfs.getGroupingSeparator());
+ sb.append(dfs.getInfinity());
+ sb.append(dfs.getInternationalCurrencySymbol());
+ sb.append(dfs.getMinusSign());
+ sb.append(dfs.getMonetaryDecimalSeparator());
+ sb.append(dfs.getNaN());
+ sb.append(dfs.getPercent());
+ sb.append('+');
+ specialChars = sb.toString();
+ }
+ else {
+ specialChars = "";
+ }
+ }
+
+ /**
+ * Invokes <code>parseObject</code> on <code>f</code>, returning
+ * its value.
+ */
+ Object stringToValue(String text, Format f) throws ParseException {
+ if (f == null) {
+ return text;
+ }
+ Object value = f.parseObject(text);
+
+ return convertValueToValueClass(value, getValueClass());
+ }
+
+ /**
+ * Converts the passed in value to the passed in class. This only
+ * works if <code>valueClass</code> is one of <code>Integer</code>,
+ * <code>Long</code>, <code>Float</code>, <code>Double</code>,
+ * <code>Byte</code> or <code>Short</code> and <code>value</code>
+ * is an instanceof <code>Number</code>.
+ */
+ private Object convertValueToValueClass(Object value, Class valueClass) {
+ if (valueClass != null && (value instanceof Number)) {
+ if (valueClass == Integer.class) {
+ return new Integer(((Number)value).intValue());
+ }
+ else if (valueClass == Long.class) {
+ return new Long(((Number)value).longValue());
+ }
+ else if (valueClass == Float.class) {
+ return new Float(((Number)value).floatValue());
+ }
+ else if (valueClass == Double.class) {
+ return new Double(((Number)value).doubleValue());
+ }
+ else if (valueClass == Byte.class) {
+ return new Byte(((Number)value).byteValue());
+ }
+ else if (valueClass == Short.class) {
+ return new Short(((Number)value).shortValue());
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Returns the character that is used to toggle to positive values.
+ */
+ private char getPositiveSign() {
+ return '+';
+ }
+
+ /**
+ * Returns the character that is used to toggle to negative values.
+ */
+ private char getMinusSign() {
+ DecimalFormatSymbols dfs = getDecimalFormatSymbols();
+
+ if (dfs != null) {
+ return dfs.getMinusSign();
+ }
+ return '-';
+ }
+
+ /**
+ * Returns the character that is used to toggle to negative values.
+ */
+ private char getDecimalSeparator() {
+ DecimalFormatSymbols dfs = getDecimalFormatSymbols();
+
+ if (dfs != null) {
+ return dfs.getDecimalSeparator();
+ }
+ return '.';
+ }
+
+ /**
+ * Returns the DecimalFormatSymbols from the Format instance.
+ */
+ private DecimalFormatSymbols getDecimalFormatSymbols() {
+ Format f = getFormat();
+
+ if (f instanceof DecimalFormat) {
+ return ((DecimalFormat)f).getDecimalFormatSymbols();
+ }
+ return null;
+ }
+
+ /**
+ */
+ private boolean isValidInsertionCharacter(char aChar) {
+ return (Character.isDigit(aChar) || specialChars.indexOf(aChar) != -1);
+ }
+
+
+ /**
+ * Subclassed to return false if <code>text</code> contains in an invalid
+ * character to insert, that is, it is not a digit
+ * (<code>Character.isDigit()</code>) and
+ * not one of the characters defined by the DecimalFormatSymbols.
+ */
+ boolean isLegalInsertText(String text) {
+ if (getAllowsInvalid()) {
+ return true;
+ }
+ for (int counter = text.length() - 1; counter >= 0; counter--) {
+ char aChar = text.charAt(counter);
+
+ if (!Character.isDigit(aChar) &&
+ specialChars.indexOf(aChar) == -1){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Subclassed to treat the decimal separator, grouping separator,
+ * exponent symbol, percent, permille, currency and sign as literals.
+ */
+ boolean isLiteral(Map attrs) {
+ if (!super.isLiteral(attrs)) {
+ if (attrs == null) {
+ return false;
+ }
+ int size = attrs.size();
+
+ if (attrs.get(NumberFormat.Field.GROUPING_SEPARATOR) != null) {
+ size--;
+ if (attrs.get(NumberFormat.Field.INTEGER) != null) {
+ size--;
+ }
+ }
+ if (attrs.get(NumberFormat.Field.EXPONENT_SYMBOL) != null) {
+ size--;
+ }
+ if (attrs.get(NumberFormat.Field.PERCENT) != null) {
+ size--;
+ }
+ if (attrs.get(NumberFormat.Field.PERMILLE) != null) {
+ size--;
+ }
+ if (attrs.get(NumberFormat.Field.CURRENCY) != null) {
+ size--;
+ }
+ if (attrs.get(NumberFormat.Field.SIGN) != null) {
+ size--;
+ }
+ if (size == 0) {
+ return true;
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Subclassed to make the decimal separator navigatable, as well
+ * as making the character between the integer field and the next
+ * field navigatable.
+ */
+ boolean isNavigatable(int index) {
+ if (!super.isNavigatable(index)) {
+ // Don't skip the decimal, it causes wierd behavior
+ if (getBufferedChar(index) == getDecimalSeparator()) {
+ return true;
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns the first <code>NumberFormat.Field</code> starting
+ * <code>index</code> incrementing by <code>direction</code>.
+ */
+ private NumberFormat.Field getFieldFrom(int index, int direction) {
+ if (isValidMask()) {
+ int max = getFormattedTextField().getDocument().getLength();
+ AttributedCharacterIterator iterator = getIterator();
+
+ if (index >= max) {
+ index += direction;
+ }
+ while (index >= 0 && index < max) {
+ iterator.setIndex(index);
+
+ Map attrs = iterator.getAttributes();
+
+ if (attrs != null && attrs.size() > 0) {
+ Iterator keys = attrs.keySet().iterator();
+
+ while (keys.hasNext()) {
+ Object key = keys.next();
+
+ if (key instanceof NumberFormat.Field) {
+ return (NumberFormat.Field)key;
+ }
+ }
+ }
+ index += direction;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Overriden to toggle the value if the positive/minus sign
+ * is inserted.
+ */
+ void replace(DocumentFilter.FilterBypass fb, int offset, int length,
+ String string, AttributeSet attr) throws BadLocationException {
+ if (!getAllowsInvalid() && length == 0 && string != null &&
+ string.length() == 1 &&
+ toggleSignIfNecessary(fb, offset, string.charAt(0))) {
+ return;
+ }
+ super.replace(fb, offset, length, string, attr);
+ }
+
+ /**
+ * Will change the sign of the integer or exponent field if
+ * <code>aChar</code> is the positive or minus sign. Returns
+ * true if a sign change was attempted.
+ */
+ private boolean toggleSignIfNecessary(DocumentFilter.FilterBypass fb,
+ int offset, char aChar) throws
+ BadLocationException {
+ if (aChar == getMinusSign() || aChar == getPositiveSign()) {
+ NumberFormat.Field field = getFieldFrom(offset, -1);
+ Object newValue;
+
+ try {
+ if (field == null ||
+ (field != NumberFormat.Field.EXPONENT &&
+ field != NumberFormat.Field.EXPONENT_SYMBOL &&
+ field != NumberFormat.Field.EXPONENT_SIGN)) {
+ newValue = toggleSign((aChar == getPositiveSign()));
+ }
+ else {
+ // exponent
+ newValue = toggleExponentSign(offset, aChar);
+ }
+ if (newValue != null && isValidValue(newValue, false)) {
+ int lc = getLiteralCountTo(offset);
+ String string = valueToString(newValue);
+
+ fb.remove(0, fb.getDocument().getLength());
+ fb.insertString(0, string, null);
+ updateValue(newValue);
+ repositionCursor(getLiteralCountTo(offset) -
+ lc + offset, 1);
+ return true;
+ }
+ } catch (ParseException pe) {
+ invalidEdit();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the range offset to length identifies the only
+ * integer field.
+ */
+ private boolean isOnlyIntegerField(int offset, int length) {
+ if (isValidMask()) {
+ int start = getAttributeStart(NumberFormat.Field.INTEGER);
+
+ if (start != -1) {
+ AttributedCharacterIterator iterator = getIterator();
+
+ iterator.setIndex(start);
+ if (offset > start || iterator.getRunLimit(
+ NumberFormat.Field.INTEGER) > (offset + length)) {
+ return false;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Invoked to toggle the sign. For this to work the value class
+ * must have a single arg constructor that takes a String.
+ */
+ private Object toggleSign(boolean positive) throws ParseException {
+ Object value = stringToValue(getFormattedTextField().getText());
+
+ if (value != null) {
+ // toString isn't localized, so that using +/- should work
+ // correctly.
+ String string = value.toString();
+
+ if (string != null && string.length() > 0) {
+ if (positive) {
+ if (string.charAt(0) == '-') {
+ string = string.substring(1);
+ }
+ }
+ else {
+ if (string.charAt(0) == '+') {
+ string = string.substring(1);
+ }
+ if (string.length() > 0 && string.charAt(0) != '-') {
+ string = "-" + string;
+ }
+ }
+ if (string != null) {
+ Class valueClass = getValueClass();
+
+ if (valueClass == null) {
+ valueClass = value.getClass();
+ }
+ try {
+ Constructor cons = valueClass.getConstructor(
+ new Class[] { String.class });
+
+ if (cons != null) {
+ return cons.newInstance(new Object[]{string});
+ }
+ } catch (Throwable ex) { }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Invoked to toggle the sign of the exponent (for scientific
+ * numbers).
+ */
+ private Object toggleExponentSign(int offset, char aChar) throws
+ BadLocationException, ParseException {
+ String string = getFormattedTextField().getText();
+ int replaceLength = 0;
+ int loc = getAttributeStart(NumberFormat.Field.EXPONENT_SIGN);
+
+ if (loc >= 0) {
+ replaceLength = 1;
+ offset = loc;
+ }
+ if (aChar == getPositiveSign()) {
+ string = getReplaceString(offset, replaceLength, null);
+ }
+ else {
+ string = getReplaceString(offset, replaceLength,
+ new String(new char[] { aChar }));
+ }
+ return stringToValue(string);
+ }
+}