Home » openjdk-7 » javax » swing » text » [javadoc | source]

    1   /*
    2    * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   package javax.swing.text;
   26   
   27   import java.lang.reflect;
   28   import java.text;
   29   import java.util;
   30   
   31   /**
   32    * <code>NumberFormatter</code> subclasses <code>InternationalFormatter</code>
   33    * adding special behavior for numbers. Among the specializations are
   34    * (these are only used if the <code>NumberFormatter</code> does not display
   35    * invalid nubers, eg <code>setAllowsInvalid(false)</code>):
   36    * <ul>
   37    *   <li>Pressing +/- (- is determined from the
   38    *       <code>DecimalFormatSymbols</code> associated with the
   39    *       <code>DecimalFormat</code>) in any field but the exponent
   40    *       field will attempt to change the sign of the number to
   41    *       positive/negative.
   42    *   <li>Pressing +/- (- is determined from the
   43    *       <code>DecimalFormatSymbols</code> associated with the
   44    *       <code>DecimalFormat</code>) in the exponent field will
   45    *       attemp to change the sign of the exponent to positive/negative.
   46    * </ul>
   47    * <p>
   48    * If you are displaying scientific numbers, you may wish to turn on
   49    * overwrite mode, <code>setOverwriteMode(true)</code>. For example:
   50    * <pre>
   51    * DecimalFormat decimalFormat = new DecimalFormat("0.000E0");
   52    * NumberFormatter textFormatter = new NumberFormatter(decimalFormat);
   53    * textFormatter.setOverwriteMode(true);
   54    * textFormatter.setAllowsInvalid(false);
   55    * </pre>
   56    * <p>
   57    * If you are going to allow the user to enter decimal
   58    * values, you should either force the DecimalFormat to contain at least
   59    * one decimal (<code>#.0###</code>), or allow the value to be invalid
   60    * <code>setAllowsInvalid(true)</code>. Otherwise users may not be able to
   61    * input decimal values.
   62    * <p>
   63    * <code>NumberFormatter</code> provides slightly different behavior to
   64    * <code>stringToValue</code> than that of its superclass. If you have
   65    * specified a Class for values, {@link #setValueClass}, that is one of
   66    * of <code>Integer</code>, <code>Long</code>, <code>Float</code>,
   67    * <code>Double</code>, <code>Byte</code> or <code>Short</code> and
   68    * the Format's <code>parseObject</code> returns an instance of
   69    * <code>Number</code>, the corresponding instance of the value class
   70    * will be created using the constructor appropriate for the primitive
   71    * type the value class represents. For example:
   72    * <code>setValueClass(Integer.class)</code> will cause the resulting
   73    * value to be created via
   74    * <code>new Integer(((Number)formatter.parseObject(string)).intValue())</code>.
   75    * This is typically useful if you
   76    * wish to set a min/max value as the various <code>Number</code>
   77    * implementations are generally not comparable to each other. This is also
   78    * useful if for some reason you need a specific <code>Number</code>
   79    * implementation for your values.
   80    * <p>
   81    * <strong>Warning:</strong>
   82    * Serialized objects of this class will not be compatible with
   83    * future Swing releases. The current serialization support is
   84    * appropriate for short term storage or RMI between applications running
   85    * the same version of Swing.  As of 1.4, support for long term storage
   86    * of all JavaBeans<sup><font size="-2">TM</font></sup>
   87    * has been added to the <code>java.beans</code> package.
   88    * Please see {@link java.beans.XMLEncoder}.
   89    *
   90    * @since 1.4
   91    */
   92   public class NumberFormatter extends InternationalFormatter {
   93       /** The special characters from the Format instance. */
   94       private String specialChars;
   95   
   96       /**
   97        * Creates a <code>NumberFormatter</code> with the a default
   98        * <code>NumberFormat</code> instance obtained from
   99        * <code>NumberFormat.getNumberInstance()</code>.
  100        */
  101       public NumberFormatter() {
  102           this(NumberFormat.getNumberInstance());
  103       }
  104   
  105       /**
  106        * Creates a NumberFormatter with the specified Format instance.
  107        *
  108        * @param format Format used to dictate legal values
  109        */
  110       public NumberFormatter(NumberFormat format) {
  111           super(format);
  112           setFormat(format);
  113           setAllowsInvalid(true);
  114           setCommitsOnValidEdit(false);
  115           setOverwriteMode(false);
  116       }
  117   
  118       /**
  119        * Sets the format that dictates the legal values that can be edited
  120        * and displayed.
  121        * <p>
  122        * If you have used the nullary constructor the value of this property
  123        * will be determined for the current locale by way of the
  124        * <code>NumberFormat.getNumberInstance()</code> method.
  125        *
  126        * @param format NumberFormat instance used to dictate legal values
  127        */
  128       public void setFormat(Format format) {
  129           super.setFormat(format);
  130   
  131           DecimalFormatSymbols dfs = getDecimalFormatSymbols();
  132   
  133           if (dfs != null) {
  134               StringBuilder sb = new StringBuilder();
  135   
  136               sb.append(dfs.getCurrencySymbol());
  137               sb.append(dfs.getDecimalSeparator());
  138               sb.append(dfs.getGroupingSeparator());
  139               sb.append(dfs.getInfinity());
  140               sb.append(dfs.getInternationalCurrencySymbol());
  141               sb.append(dfs.getMinusSign());
  142               sb.append(dfs.getMonetaryDecimalSeparator());
  143               sb.append(dfs.getNaN());
  144               sb.append(dfs.getPercent());
  145               sb.append('+');
  146               specialChars = sb.toString();
  147           }
  148           else {
  149               specialChars = "";
  150           }
  151       }
  152   
  153       /**
  154        * Invokes <code>parseObject</code> on <code>f</code>, returning
  155        * its value.
  156        */
  157       Object stringToValue(String text, Format f) throws ParseException {
  158           if (f == null) {
  159               return text;
  160           }
  161           Object value = f.parseObject(text);
  162   
  163           return convertValueToValueClass(value, getValueClass());
  164       }
  165   
  166       /**
  167        * Converts the passed in value to the passed in class. This only
  168        * works if <code>valueClass</code> is one of <code>Integer</code>,
  169        * <code>Long</code>, <code>Float</code>, <code>Double</code>,
  170        * <code>Byte</code> or <code>Short</code> and <code>value</code>
  171        * is an instanceof <code>Number</code>.
  172        */
  173       private Object convertValueToValueClass(Object value, Class valueClass) {
  174           if (valueClass != null && (value instanceof Number)) {
  175               Number numberValue = (Number)value;
  176               if (valueClass == Integer.class) {
  177                   return Integer.valueOf(numberValue.intValue());
  178               }
  179               else if (valueClass == Long.class) {
  180                   return Long.valueOf(numberValue.longValue());
  181               }
  182               else if (valueClass == Float.class) {
  183                   return Float.valueOf(numberValue.floatValue());
  184               }
  185               else if (valueClass == Double.class) {
  186                   return Double.valueOf(numberValue.doubleValue());
  187               }
  188               else if (valueClass == Byte.class) {
  189                   return Byte.valueOf(numberValue.byteValue());
  190               }
  191               else if (valueClass == Short.class) {
  192                   return Short.valueOf(numberValue.shortValue());
  193               }
  194           }
  195           return value;
  196       }
  197   
  198       /**
  199        * Returns the character that is used to toggle to positive values.
  200        */
  201       private char getPositiveSign() {
  202           return '+';
  203       }
  204   
  205       /**
  206        * Returns the character that is used to toggle to negative values.
  207        */
  208       private char getMinusSign() {
  209           DecimalFormatSymbols dfs = getDecimalFormatSymbols();
  210   
  211           if (dfs != null) {
  212               return dfs.getMinusSign();
  213           }
  214           return '-';
  215       }
  216   
  217       /**
  218        * Returns the character that is used to toggle to negative values.
  219        */
  220       private char getDecimalSeparator() {
  221           DecimalFormatSymbols dfs = getDecimalFormatSymbols();
  222   
  223           if (dfs != null) {
  224               return dfs.getDecimalSeparator();
  225           }
  226           return '.';
  227       }
  228   
  229       /**
  230        * Returns the DecimalFormatSymbols from the Format instance.
  231        */
  232       private DecimalFormatSymbols getDecimalFormatSymbols() {
  233           Format f = getFormat();
  234   
  235           if (f instanceof DecimalFormat) {
  236               return ((DecimalFormat)f).getDecimalFormatSymbols();
  237           }
  238           return null;
  239       }
  240   
  241       /**
  242        * Subclassed to return false if <code>text</code> contains in an invalid
  243        * character to insert, that is, it is not a digit
  244        * (<code>Character.isDigit()</code>) and
  245        * not one of the characters defined by the DecimalFormatSymbols.
  246        */
  247       boolean isLegalInsertText(String text) {
  248           if (getAllowsInvalid()) {
  249               return true;
  250           }
  251           for (int counter = text.length() - 1; counter >= 0; counter--) {
  252               char aChar = text.charAt(counter);
  253   
  254               if (!Character.isDigit(aChar) &&
  255                              specialChars.indexOf(aChar) == -1){
  256                   return false;
  257               }
  258           }
  259           return true;
  260       }
  261   
  262       /**
  263        * Subclassed to treat the decimal separator, grouping separator,
  264        * exponent symbol, percent, permille, currency and sign as literals.
  265        */
  266       boolean isLiteral(Map attrs) {
  267           if (!super.isLiteral(attrs)) {
  268               if (attrs == null) {
  269                   return false;
  270               }
  271               int size = attrs.size();
  272   
  273               if (attrs.get(NumberFormat.Field.GROUPING_SEPARATOR) != null) {
  274                   size--;
  275                   if (attrs.get(NumberFormat.Field.INTEGER) != null) {
  276                       size--;
  277                   }
  278               }
  279               if (attrs.get(NumberFormat.Field.EXPONENT_SYMBOL) != null) {
  280                   size--;
  281               }
  282               if (attrs.get(NumberFormat.Field.PERCENT) != null) {
  283                   size--;
  284               }
  285               if (attrs.get(NumberFormat.Field.PERMILLE) != null) {
  286                   size--;
  287               }
  288               if (attrs.get(NumberFormat.Field.CURRENCY) != null) {
  289                   size--;
  290               }
  291               if (attrs.get(NumberFormat.Field.SIGN) != null) {
  292                   size--;
  293               }
  294               return size == 0;
  295           }
  296           return true;
  297       }
  298   
  299       /**
  300        * Subclassed to make the decimal separator navigatable, as well
  301        * as making the character between the integer field and the next
  302        * field navigatable.
  303        */
  304       boolean isNavigatable(int index) {
  305           if (!super.isNavigatable(index)) {
  306               // Don't skip the decimal, it causes wierd behavior
  307               return getBufferedChar(index) == getDecimalSeparator();
  308           }
  309           return true;
  310       }
  311   
  312       /**
  313        * Returns the first <code>NumberFormat.Field</code> starting
  314        * <code>index</code> incrementing by <code>direction</code>.
  315        */
  316       private NumberFormat.Field getFieldFrom(int index, int direction) {
  317           if (isValidMask()) {
  318               int max = getFormattedTextField().getDocument().getLength();
  319               AttributedCharacterIterator iterator = getIterator();
  320   
  321               if (index >= max) {
  322                   index += direction;
  323               }
  324               while (index >= 0 && index < max) {
  325                   iterator.setIndex(index);
  326   
  327                   Map attrs = iterator.getAttributes();
  328   
  329                   if (attrs != null && attrs.size() > 0) {
  330                       for (Object key : attrs.keySet()) {
  331                           if (key instanceof NumberFormat.Field) {
  332                               return (NumberFormat.Field)key;
  333                           }
  334                       }
  335                   }
  336                   index += direction;
  337               }
  338           }
  339           return null;
  340       }
  341   
  342       /**
  343        * Overriden to toggle the value if the positive/minus sign
  344        * is inserted.
  345        */
  346       void replace(DocumentFilter.FilterBypass fb, int offset, int length,
  347                   String string, AttributeSet attr) throws BadLocationException {
  348           if (!getAllowsInvalid() && length == 0 && string != null &&
  349               string.length() == 1 &&
  350               toggleSignIfNecessary(fb, offset, string.charAt(0))) {
  351               return;
  352           }
  353           super.replace(fb, offset, length, string, attr);
  354       }
  355   
  356       /**
  357        * Will change the sign of the integer or exponent field if
  358        * <code>aChar</code> is the positive or minus sign. Returns
  359        * true if a sign change was attempted.
  360        */
  361       private boolean toggleSignIfNecessary(DocumentFilter.FilterBypass fb,
  362                                                 int offset, char aChar) throws
  363                                 BadLocationException {
  364           if (aChar == getMinusSign() || aChar == getPositiveSign()) {
  365               NumberFormat.Field field = getFieldFrom(offset, -1);
  366               Object newValue;
  367   
  368               try {
  369                   if (field == null ||
  370                       (field != NumberFormat.Field.EXPONENT &&
  371                        field != NumberFormat.Field.EXPONENT_SYMBOL &&
  372                        field != NumberFormat.Field.EXPONENT_SIGN)) {
  373                       newValue = toggleSign((aChar == getPositiveSign()));
  374                   }
  375                   else {
  376                       // exponent
  377                       newValue = toggleExponentSign(offset, aChar);
  378                   }
  379                   if (newValue != null && isValidValue(newValue, false)) {
  380                       int lc = getLiteralCountTo(offset);
  381                       String string = valueToString(newValue);
  382   
  383                       fb.remove(0, fb.getDocument().getLength());
  384                       fb.insertString(0, string, null);
  385                       updateValue(newValue);
  386                       repositionCursor(getLiteralCountTo(offset) -
  387                                        lc + offset, 1);
  388                       return true;
  389                   }
  390               } catch (ParseException pe) {
  391                   invalidEdit();
  392               }
  393           }
  394           return false;
  395       }
  396   
  397       /**
  398        * Invoked to toggle the sign. For this to work the value class
  399        * must have a single arg constructor that takes a String.
  400        */
  401       private Object toggleSign(boolean positive) throws ParseException {
  402           Object value = stringToValue(getFormattedTextField().getText());
  403   
  404           if (value != null) {
  405               // toString isn't localized, so that using +/- should work
  406               // correctly.
  407               String string = value.toString();
  408   
  409               if (string != null && string.length() > 0) {
  410                   if (positive) {
  411                       if (string.charAt(0) == '-') {
  412                           string = string.substring(1);
  413                       }
  414                   }
  415                   else {
  416                       if (string.charAt(0) == '+') {
  417                           string = string.substring(1);
  418                       }
  419                       if (string.length() > 0 && string.charAt(0) != '-') {
  420                           string = "-" + string;
  421                       }
  422                   }
  423                   if (string != null) {
  424                       Class<?> valueClass = getValueClass();
  425   
  426                       if (valueClass == null) {
  427                           valueClass = value.getClass();
  428                       }
  429                       try {
  430                           Constructor cons = valueClass.getConstructor(
  431                                                 new Class[] { String.class });
  432   
  433                           if (cons != null) {
  434                               return cons.newInstance(new Object[]{string});
  435                           }
  436                       } catch (Throwable ex) { }
  437                   }
  438               }
  439           }
  440           return null;
  441       }
  442   
  443       /**
  444        * Invoked to toggle the sign of the exponent (for scientific
  445        * numbers).
  446        */
  447       private Object toggleExponentSign(int offset, char aChar) throws
  448                                BadLocationException, ParseException {
  449           String string = getFormattedTextField().getText();
  450           int replaceLength = 0;
  451           int loc = getAttributeStart(NumberFormat.Field.EXPONENT_SIGN);
  452   
  453           if (loc >= 0) {
  454               replaceLength = 1;
  455               offset = loc;
  456           }
  457           if (aChar == getPositiveSign()) {
  458               string = getReplaceString(offset, replaceLength, null);
  459           }
  460           else {
  461               string = getReplaceString(offset, replaceLength,
  462                                         new String(new char[] { aChar }));
  463           }
  464           return stringToValue(string);
  465       }
  466   }

Home » openjdk-7 » javax » swing » text » [javadoc | source]