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