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.awt.event.ActionEvent;
   28   import java.io;
   29   import java.text;
   30   import java.text.AttributedCharacterIterator.Attribute;
   31   import java.util;
   32   import javax.swing;
   33   
   34   /**
   35    * <code>InternationalFormatter</code> extends <code>DefaultFormatter</code>,
   36    * using an instance of <code>java.text.Format</code> to handle the
   37    * conversion to a String, and the conversion from a String.
   38    * <p>
   39    * If <code>getAllowsInvalid()</code> is false, this will ask the
   40    * <code>Format</code> to format the current text on every edit.
   41    * <p>
   42    * You can specify a minimum and maximum value by way of the
   43    * <code>setMinimum</code> and <code>setMaximum</code> methods. In order
   44    * for this to work the values returned from <code>stringToValue</code> must be
   45    * comparable to the min/max values by way of the <code>Comparable</code>
   46    * interface.
   47    * <p>
   48    * Be careful how you configure the <code>Format</code> and the
   49    * <code>InternationalFormatter</code>, as it is possible to create a
   50    * situation where certain values can not be input. Consider the date
   51    * format 'M/d/yy', an <code>InternationalFormatter</code> that is always
   52    * valid (<code>setAllowsInvalid(false)</code>), is in overwrite mode
   53    * (<code>setOverwriteMode(true)</code>) and the date 7/1/99. In this
   54    * case the user will not be able to enter a two digit month or day of
   55    * month. To avoid this, the format should be 'MM/dd/yy'.
   56    * <p>
   57    * If <code>InternationalFormatter</code> is configured to only allow valid
   58    * values (<code>setAllowsInvalid(false)</code>), every valid edit will result
   59    * in the text of the <code>JFormattedTextField</code> being completely reset
   60    * from the <code>Format</code>.
   61    * The cursor position will also be adjusted as literal characters are
   62    * added/removed from the resulting String.
   63    * <p>
   64    * <code>InternationalFormatter</code>'s behavior of
   65    * <code>stringToValue</code> is  slightly different than that of
   66    * <code>DefaultTextFormatter</code>, it does the following:
   67    * <ol>
   68    *   <li><code>parseObject</code> is invoked on the <code>Format</code>
   69    *       specified by <code>setFormat</code>
   70    *   <li>If a Class has been set for the values (<code>setValueClass</code>),
   71    *       supers implementation is invoked to convert the value returned
   72    *       from <code>parseObject</code> to the appropriate class.
   73    *   <li>If a <code>ParseException</code> has not been thrown, and the value
   74    *       is outside the min/max a <code>ParseException</code> is thrown.
   75    *   <li>The value is returned.
   76    * </ol>
   77    * <code>InternationalFormatter</code> implements <code>stringToValue</code>
   78    * in this manner so that you can specify an alternate Class than
   79    * <code>Format</code> may return.
   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    * @see java.text.Format
   91    * @see java.lang.Comparable
   92    *
   93    * @since 1.4
   94    */
   95   public class InternationalFormatter extends DefaultFormatter {
   96       /**
   97        * Used by <code>getFields</code>.
   98        */
   99       private static final Format.Field[] EMPTY_FIELD_ARRAY =new Format.Field[0];
  100   
  101       /**
  102        * Object used to handle the conversion.
  103        */
  104       private Format format;
  105       /**
  106        * Can be used to impose a maximum value.
  107        */
  108       private Comparable max;
  109       /**
  110        * Can be used to impose a minimum value.
  111        */
  112       private Comparable min;
  113   
  114       /**
  115        * <code>InternationalFormatter</code>'s behavior is dicatated by a
  116        * <code>AttributedCharacterIterator</code> that is obtained from
  117        * the <code>Format</code>. On every edit, assuming
  118        * allows invalid is false, the <code>Format</code> instance is invoked
  119        * with <code>formatToCharacterIterator</code>. A <code>BitSet</code> is
  120        * also kept upto date with the non-literal characters, that is
  121        * for every index in the <code>AttributedCharacterIterator</code> an
  122        * entry in the bit set is updated based on the return value from
  123        * <code>isLiteral(Map)</code>. <code>isLiteral(int)</code> then uses
  124        * this cached information.
  125        * <p>
  126        * If allowsInvalid is false, every edit results in resetting the complete
  127        * text of the JTextComponent.
  128        * <p>
  129        * InternationalFormatterFilter can also provide two actions suitable for
  130        * incrementing and decrementing. To enable this a subclass must
  131        * override <code>getSupportsIncrement</code> to return true, and
  132        * override <code>adjustValue</code> to handle the changing of the
  133        * value. If you want to support changing the value outside of
  134        * the valid FieldPositions, you will need to override
  135        * <code>canIncrement</code>.
  136        */
  137       /**
  138        * A bit is set for every index identified in the
  139        * AttributedCharacterIterator that is not considered decoration.
  140        * This should only be used if validMask is true.
  141        */
  142       private transient BitSet literalMask;
  143       /**
  144        * Used to iterate over characters.
  145        */
  146       private transient AttributedCharacterIterator iterator;
  147       /**
  148        * True if the Format was able to convert the value to a String and
  149        * back.
  150        */
  151       private transient boolean validMask;
  152       /**
  153        * Current value being displayed.
  154        */
  155       private transient String string;
  156       /**
  157        * If true, DocumentFilter methods are unconditionally allowed,
  158        * and no checking is done on their values. This is used when
  159        * incrementing/decrementing via the actions.
  160        */
  161       private transient boolean ignoreDocumentMutate;
  162   
  163   
  164       /**
  165        * Creates an <code>InternationalFormatter</code> with no
  166        * <code>Format</code> specified.
  167        */
  168       public InternationalFormatter() {
  169           setOverwriteMode(false);
  170       }
  171   
  172       /**
  173        * Creates an <code>InternationalFormatter</code> with the specified
  174        * <code>Format</code> instance.
  175        *
  176        * @param format Format instance used for converting from/to Strings
  177        */
  178       public InternationalFormatter(Format format) {
  179           this();
  180           setFormat(format);
  181       }
  182   
  183       /**
  184        * Sets the format that dictates the legal values that can be edited
  185        * and displayed.
  186        *
  187        * @param format <code>Format</code> instance used for converting
  188        * from/to Strings
  189        */
  190       public void setFormat(Format format) {
  191           this.format = format;
  192       }
  193   
  194       /**
  195        * Returns the format that dictates the legal values that can be edited
  196        * and displayed.
  197        *
  198        * @return Format instance used for converting from/to Strings
  199        */
  200       public Format getFormat() {
  201           return format;
  202       }
  203   
  204       /**
  205        * Sets the minimum permissible value. If the <code>valueClass</code> has
  206        * not been specified, and <code>minimum</code> is non null, the
  207        * <code>valueClass</code> will be set to that of the class of
  208        * <code>minimum</code>.
  209        *
  210        * @param minimum Minimum legal value that can be input
  211        * @see #setValueClass
  212        */
  213       public void setMinimum(Comparable minimum) {
  214           if (getValueClass() == null && minimum != null) {
  215               setValueClass(minimum.getClass());
  216           }
  217           min = minimum;
  218       }
  219   
  220       /**
  221        * Returns the minimum permissible value.
  222        *
  223        * @return Minimum legal value that can be input
  224        */
  225       public Comparable getMinimum() {
  226           return min;
  227       }
  228   
  229       /**
  230        * Sets the maximum permissible value. If the <code>valueClass</code> has
  231        * not been specified, and <code>max</code> is non null, the
  232        * <code>valueClass</code> will be set to that of the class of
  233        * <code>max</code>.
  234        *
  235        * @param max Maximum legal value that can be input
  236        * @see #setValueClass
  237        */
  238       public void setMaximum(Comparable max) {
  239           if (getValueClass() == null && max != null) {
  240               setValueClass(max.getClass());
  241           }
  242           this.max = max;
  243       }
  244   
  245       /**
  246        * Returns the maximum permissible value.
  247        *
  248        * @return Maximum legal value that can be input
  249        */
  250       public Comparable getMaximum() {
  251           return max;
  252       }
  253   
  254       /**
  255        * Installs the <code>DefaultFormatter</code> onto a particular
  256        * <code>JFormattedTextField</code>.
  257        * This will invoke <code>valueToString</code> to convert the
  258        * current value from the <code>JFormattedTextField</code> to
  259        * a String. This will then install the <code>Action</code>s from
  260        * <code>getActions</code>, the <code>DocumentFilter</code>
  261        * returned from <code>getDocumentFilter</code> and the
  262        * <code>NavigationFilter</code> returned from
  263        * <code>getNavigationFilter</code> onto the
  264        * <code>JFormattedTextField</code>.
  265        * <p>
  266        * Subclasses will typically only need to override this if they
  267        * wish to install additional listeners on the
  268        * <code>JFormattedTextField</code>.
  269        * <p>
  270        * If there is a <code>ParseException</code> in converting the
  271        * current value to a String, this will set the text to an empty
  272        * String, and mark the <code>JFormattedTextField</code> as being
  273        * in an invalid state.
  274        * <p>
  275        * While this is a public method, this is typically only useful
  276        * for subclassers of <code>JFormattedTextField</code>.
  277        * <code>JFormattedTextField</code> will invoke this method at
  278        * the appropriate times when the value changes, or its internal
  279        * state changes.
  280        *
  281        * @param ftf JFormattedTextField to format for, may be null indicating
  282        *            uninstall from current JFormattedTextField.
  283        */
  284       public void install(JFormattedTextField ftf) {
  285           super.install(ftf);
  286           updateMaskIfNecessary();
  287           // invoked again as the mask should now be valid.
  288           positionCursorAtInitialLocation();
  289       }
  290   
  291       /**
  292        * Returns a String representation of the Object <code>value</code>.
  293        * This invokes <code>format</code> on the current <code>Format</code>.
  294        *
  295        * @throws ParseException if there is an error in the conversion
  296        * @param value Value to convert
  297        * @return String representation of value
  298        */
  299       public String valueToString(Object value) throws ParseException {
  300           if (value == null) {
  301               return "";
  302           }
  303           Format f = getFormat();
  304   
  305           if (f == null) {
  306               return value.toString();
  307           }
  308           return f.format(value);
  309       }
  310   
  311       /**
  312        * Returns the <code>Object</code> representation of the
  313        * <code>String</code> <code>text</code>.
  314        *
  315        * @param text <code>String</code> to convert
  316        * @return <code>Object</code> representation of text
  317        * @throws ParseException if there is an error in the conversion
  318        */
  319       public Object stringToValue(String text) throws ParseException {
  320           Object value = stringToValue(text, getFormat());
  321   
  322           // Convert to the value class if the Value returned from the
  323           // Format does not match.
  324           if (value != null && getValueClass() != null &&
  325                                !getValueClass().isInstance(value)) {
  326               value = super.stringToValue(value.toString());
  327           }
  328           try {
  329               if (!isValidValue(value, true)) {
  330                   throw new ParseException("Value not within min/max range", 0);
  331               }
  332           } catch (ClassCastException cce) {
  333               throw new ParseException("Class cast exception comparing values: "
  334                                        + cce, 0);
  335           }
  336           return value;
  337       }
  338   
  339       /**
  340        * Returns the <code>Format.Field</code> constants associated with
  341        * the text at <code>offset</code>. If <code>offset</code> is not
  342        * a valid location into the current text, this will return an
  343        * empty array.
  344        *
  345        * @param offset offset into text to be examined
  346        * @return Format.Field constants associated with the text at the
  347        *         given position.
  348        */
  349       public Format.Field[] getFields(int offset) {
  350           if (getAllowsInvalid()) {
  351               // This will work if the currently edited value is valid.
  352               updateMask();
  353           }
  354   
  355           Map<Attribute, Object> attrs = getAttributes(offset);
  356   
  357           if (attrs != null && attrs.size() > 0) {
  358               ArrayList<Attribute> al = new ArrayList<Attribute>();
  359   
  360               al.addAll(attrs.keySet());
  361               return al.toArray(EMPTY_FIELD_ARRAY);
  362           }
  363           return EMPTY_FIELD_ARRAY;
  364       }
  365   
  366       /**
  367        * Creates a copy of the DefaultFormatter.
  368        *
  369        * @return copy of the DefaultFormatter
  370        */
  371       public Object clone() throws CloneNotSupportedException {
  372           InternationalFormatter formatter = (InternationalFormatter)super.
  373                                              clone();
  374   
  375           formatter.literalMask = null;
  376           formatter.iterator = null;
  377           formatter.validMask = false;
  378           formatter.string = null;
  379           return formatter;
  380       }
  381   
  382       /**
  383        * If <code>getSupportsIncrement</code> returns true, this returns
  384        * two Actions suitable for incrementing/decrementing the value.
  385        */
  386       protected Action[] getActions() {
  387           if (getSupportsIncrement()) {
  388               return new Action[] { new IncrementAction("increment", 1),
  389                                     new IncrementAction("decrement", -1) };
  390           }
  391           return null;
  392       }
  393   
  394       /**
  395        * Invokes <code>parseObject</code> on <code>f</code>, returning
  396        * its value.
  397        */
  398       Object stringToValue(String text, Format f) throws ParseException {
  399           if (f == null) {
  400               return text;
  401           }
  402           return f.parseObject(text);
  403       }
  404   
  405       /**
  406        * Returns true if <code>value</code> is between the min/max.
  407        *
  408        * @param wantsCCE If false, and a ClassCastException is thrown in
  409        *                 comparing the values, the exception is consumed and
  410        *                 false is returned.
  411        */
  412       boolean isValidValue(Object value, boolean wantsCCE) {
  413           Comparable min = getMinimum();
  414   
  415           try {
  416               if (min != null && min.compareTo(value) > 0) {
  417                   return false;
  418               }
  419           } catch (ClassCastException cce) {
  420               if (wantsCCE) {
  421                   throw cce;
  422               }
  423               return false;
  424           }
  425   
  426           Comparable max = getMaximum();
  427           try {
  428               if (max != null && max.compareTo(value) < 0) {
  429                   return false;
  430               }
  431           } catch (ClassCastException cce) {
  432               if (wantsCCE) {
  433                   throw cce;
  434               }
  435               return false;
  436           }
  437           return true;
  438       }
  439   
  440       /**
  441        * Returns a Set of the attribute identifiers at <code>index</code>.
  442        */
  443       Map<Attribute, Object> getAttributes(int index) {
  444           if (isValidMask()) {
  445               AttributedCharacterIterator iterator = getIterator();
  446   
  447               if (index >= 0 && index <= iterator.getEndIndex()) {
  448                   iterator.setIndex(index);
  449                   return iterator.getAttributes();
  450               }
  451           }
  452           return null;
  453       }
  454   
  455   
  456       /**
  457        * Returns the start of the first run that contains the attribute
  458        * <code>id</code>. This will return <code>-1</code> if the attribute
  459        * can not be found.
  460        */
  461       int getAttributeStart(AttributedCharacterIterator.Attribute id) {
  462           if (isValidMask()) {
  463               AttributedCharacterIterator iterator = getIterator();
  464   
  465               iterator.first();
  466               while (iterator.current() != CharacterIterator.DONE) {
  467                   if (iterator.getAttribute(id) != null) {
  468                       return iterator.getIndex();
  469                   }
  470                   iterator.next();
  471               }
  472           }
  473           return -1;
  474       }
  475   
  476       /**
  477        * Returns the <code>AttributedCharacterIterator</code> used to
  478        * format the last value.
  479        */
  480       AttributedCharacterIterator getIterator() {
  481           return iterator;
  482       }
  483   
  484       /**
  485        * Updates the AttributedCharacterIterator and bitset, if necessary.
  486        */
  487       void updateMaskIfNecessary() {
  488           if (!getAllowsInvalid() && (getFormat() != null)) {
  489               if (!isValidMask()) {
  490                   updateMask();
  491               }
  492               else {
  493                   String newString = getFormattedTextField().getText();
  494   
  495                   if (!newString.equals(string)) {
  496                       updateMask();
  497                   }
  498               }
  499           }
  500       }
  501   
  502       /**
  503        * Updates the AttributedCharacterIterator by invoking
  504        * <code>formatToCharacterIterator</code> on the <code>Format</code>.
  505        * If this is successful,
  506        * <code>updateMask(AttributedCharacterIterator)</code>
  507        * is then invoked to update the internal bitmask.
  508        */
  509       void updateMask() {
  510           if (getFormat() != null) {
  511               Document doc = getFormattedTextField().getDocument();
  512   
  513               validMask = false;
  514               if (doc != null) {
  515                   try {
  516                       string = doc.getText(0, doc.getLength());
  517                   } catch (BadLocationException ble) {
  518                       string = null;
  519                   }
  520                   if (string != null) {
  521                       try {
  522                           Object value = stringToValue(string);
  523                           AttributedCharacterIterator iterator = getFormat().
  524                                     formatToCharacterIterator(value);
  525   
  526                           updateMask(iterator);
  527                       }
  528                       catch (ParseException pe) {}
  529                       catch (IllegalArgumentException iae) {}
  530                       catch (NullPointerException npe) {}
  531                   }
  532               }
  533           }
  534       }
  535   
  536       /**
  537        * Returns the number of literal characters before <code>index</code>.
  538        */
  539       int getLiteralCountTo(int index) {
  540           int lCount = 0;
  541   
  542           for (int counter = 0; counter < index; counter++) {
  543               if (isLiteral(counter)) {
  544                   lCount++;
  545               }
  546           }
  547           return lCount;
  548       }
  549   
  550       /**
  551        * Returns true if the character at index is a literal, that is
  552        * not editable.
  553        */
  554       boolean isLiteral(int index) {
  555           if (isValidMask() && index < string.length()) {
  556               return literalMask.get(index);
  557           }
  558           return false;
  559       }
  560   
  561       /**
  562        * Returns the literal character at index.
  563        */
  564       char getLiteral(int index) {
  565           if (isValidMask() && string != null && index < string.length()) {
  566               return string.charAt(index);
  567           }
  568           return (char)0;
  569       }
  570   
  571       /**
  572        * Returns true if the character at offset is navigatable too. This
  573        * is implemented in terms of <code>isLiteral</code>, subclasses
  574        * may wish to provide different behavior.
  575        */
  576       boolean isNavigatable(int offset) {
  577           return !isLiteral(offset);
  578       }
  579   
  580       /**
  581        * Overriden to update the mask after invoking supers implementation.
  582        */
  583       void updateValue(Object value) {
  584           super.updateValue(value);
  585           updateMaskIfNecessary();
  586       }
  587   
  588       /**
  589        * Overriden to unconditionally allow the replace if
  590        * ignoreDocumentMutate is true.
  591        */
  592       void replace(DocumentFilter.FilterBypass fb, int offset,
  593                        int length, String text,
  594                        AttributeSet attrs) throws BadLocationException {
  595           if (ignoreDocumentMutate) {
  596               fb.replace(offset, length, text, attrs);
  597               return;
  598           }
  599           super.replace(fb, offset, length, text, attrs);
  600       }
  601   
  602       /**
  603        * Returns the index of the next non-literal character starting at
  604        * index. If index is not a literal, it will be returned.
  605        *
  606        * @param direction Amount to increment looking for non-literal
  607        */
  608       private int getNextNonliteralIndex(int index, int direction) {
  609           int max = getFormattedTextField().getDocument().getLength();
  610   
  611           while (index >= 0 && index < max) {
  612               if (isLiteral(index)) {
  613                   index += direction;
  614               }
  615               else {
  616                   return index;
  617               }
  618           }
  619           return (direction == -1) ? 0 : max;
  620       }
  621   
  622       /**
  623        * Overriden in an attempt to honor the literals.
  624        * <p>If we do not allow invalid values and are in overwrite mode, this
  625        * {@code rh.length} is corrected as to preserve trailing literals.
  626        * If not in overwrite mode, and there is text to insert it is
  627        * inserted at the next non literal index going forward.  If there
  628        * is only text to remove, it is removed from the next non literal
  629        * index going backward.
  630        */
  631       boolean canReplace(ReplaceHolder rh) {
  632           if (!getAllowsInvalid()) {
  633               String text = rh.text;
  634               int tl = (text != null) ? text.length() : 0;
  635               JTextComponent c = getFormattedTextField();
  636   
  637               if (tl == 0 && rh.length == 1 && c.getSelectionStart() != rh.offset) {
  638                   // Backspace, adjust to actually delete next non-literal.
  639                   rh.offset = getNextNonliteralIndex(rh.offset, -1);
  640               } else if (getOverwriteMode()) {
  641                   int pos = rh.offset;
  642                   int textPos = pos;
  643                   boolean overflown = false;
  644   
  645                   for (int i = 0; i < rh.length; i++) {
  646                       while (isLiteral(pos)) pos++;
  647                       if (pos >= string.length()) {
  648                           pos = textPos;
  649                           overflown = true;
  650                           break;
  651                       }
  652                       textPos = ++pos;
  653                   }
  654                   if (overflown || c.getSelectedText() == null) {
  655                       rh.length = pos - rh.offset;
  656                   }
  657               }
  658               else if (tl > 0) {
  659                   // insert (or insert and remove)
  660                   rh.offset = getNextNonliteralIndex(rh.offset, 1);
  661               }
  662               else {
  663                   // remove only
  664                   rh.offset = getNextNonliteralIndex(rh.offset, -1);
  665               }
  666               ((ExtendedReplaceHolder)rh).endOffset = rh.offset;
  667               ((ExtendedReplaceHolder)rh).endTextLength = (rh.text != null) ?
  668                                                       rh.text.length() : 0;
  669           }
  670           else {
  671               ((ExtendedReplaceHolder)rh).endOffset = rh.offset;
  672               ((ExtendedReplaceHolder)rh).endTextLength = (rh.text != null) ?
  673                                                       rh.text.length() : 0;
  674           }
  675           boolean can = super.canReplace(rh);
  676           if (can && !getAllowsInvalid()) {
  677               ((ExtendedReplaceHolder)rh).resetFromValue(this);
  678           }
  679           return can;
  680       }
  681   
  682       /**
  683        * When in !allowsInvalid mode the text is reset on every edit, thus
  684        * supers implementation will position the cursor at the wrong position.
  685        * As such, this invokes supers implementation and then invokes
  686        * <code>repositionCursor</code> to correctly reset the cursor.
  687        */
  688       boolean replace(ReplaceHolder rh) throws BadLocationException {
  689           int start = -1;
  690           int direction = 1;
  691           int literalCount = -1;
  692   
  693           if (rh.length > 0 && (rh.text == null || rh.text.length() == 0) &&
  694                  (getFormattedTextField().getSelectionStart() != rh.offset ||
  695                      rh.length > 1)) {
  696               direction = -1;
  697           }
  698           if (!getAllowsInvalid()) {
  699               if ((rh.text == null || rh.text.length() == 0) && rh.length > 0) {
  700                   // remove
  701                   start = getFormattedTextField().getSelectionStart();
  702               }
  703               else {
  704                   start = rh.offset;
  705               }
  706               literalCount = getLiteralCountTo(start);
  707           }
  708           if (super.replace(rh)) {
  709               if (start != -1) {
  710                   int end = ((ExtendedReplaceHolder)rh).endOffset;
  711   
  712                   end += ((ExtendedReplaceHolder)rh).endTextLength;
  713                   repositionCursor(literalCount, end, direction);
  714               }
  715               else {
  716                   start = ((ExtendedReplaceHolder)rh).endOffset;
  717                   if (direction == 1) {
  718                       start += ((ExtendedReplaceHolder)rh).endTextLength;
  719                   }
  720                   repositionCursor(start, direction);
  721               }
  722               return true;
  723           }
  724           return false;
  725       }
  726   
  727       /**
  728        * Repositions the cursor. <code>startLiteralCount</code> gives
  729        * the number of literals to the start of the deleted range, end
  730        * gives the ending location to adjust from, direction gives
  731        * the direction relative to <code>end</code> to position the
  732        * cursor from.
  733        */
  734       private void repositionCursor(int startLiteralCount, int end,
  735                                     int direction)  {
  736           int endLiteralCount = getLiteralCountTo(end);
  737   
  738           if (endLiteralCount != end) {
  739               end -= startLiteralCount;
  740               for (int counter = 0; counter < end; counter++) {
  741                   if (isLiteral(counter)) {
  742                       end++;
  743                   }
  744               }
  745           }
  746           repositionCursor(end, 1 /*direction*/);
  747       }
  748   
  749       /**
  750        * Returns the character from the mask that has been buffered
  751        * at <code>index</code>.
  752        */
  753       char getBufferedChar(int index) {
  754           if (isValidMask()) {
  755               if (string != null && index < string.length()) {
  756                   return string.charAt(index);
  757               }
  758           }
  759           return (char)0;
  760       }
  761   
  762       /**
  763        * Returns true if the current mask is valid.
  764        */
  765       boolean isValidMask() {
  766           return validMask;
  767       }
  768   
  769       /**
  770        * Returns true if <code>attributes</code> is null or empty.
  771        */
  772       boolean isLiteral(Map attributes) {
  773           return ((attributes == null) || attributes.size() == 0);
  774       }
  775   
  776       /**
  777        * Updates the interal bitset from <code>iterator</code>. This will
  778        * set <code>validMask</code> to true if <code>iterator</code> is
  779        * non-null.
  780        */
  781       private void updateMask(AttributedCharacterIterator iterator) {
  782           if (iterator != null) {
  783               validMask = true;
  784               this.iterator = iterator;
  785   
  786               // Update the literal mask
  787               if (literalMask == null) {
  788                   literalMask = new BitSet();
  789               }
  790               else {
  791                   for (int counter = literalMask.length() - 1; counter >= 0;
  792                        counter--) {
  793                       literalMask.clear(counter);
  794                   }
  795               }
  796   
  797               iterator.first();
  798               while (iterator.current() != CharacterIterator.DONE) {
  799                   Map attributes = iterator.getAttributes();
  800                   boolean set = isLiteral(attributes);
  801                   int start = iterator.getIndex();
  802                   int end = iterator.getRunLimit();
  803   
  804                   while (start < end) {
  805                       if (set) {
  806                           literalMask.set(start);
  807                       }
  808                       else {
  809                           literalMask.clear(start);
  810                       }
  811                       start++;
  812                   }
  813                   iterator.setIndex(start);
  814               }
  815           }
  816       }
  817   
  818       /**
  819        * Returns true if <code>field</code> is non-null.
  820        * Subclasses that wish to allow incrementing to happen outside of
  821        * the known fields will need to override this.
  822        */
  823       boolean canIncrement(Object field, int cursorPosition) {
  824           return (field != null);
  825       }
  826   
  827       /**
  828        * Selects the fields identified by <code>attributes</code>.
  829        */
  830       void selectField(Object f, int count) {
  831           AttributedCharacterIterator iterator = getIterator();
  832   
  833           if (iterator != null &&
  834                           (f instanceof AttributedCharacterIterator.Attribute)) {
  835               AttributedCharacterIterator.Attribute field =
  836                                      (AttributedCharacterIterator.Attribute)f;
  837   
  838               iterator.first();
  839               while (iterator.current() != CharacterIterator.DONE) {
  840                   while (iterator.getAttribute(field) == null &&
  841                          iterator.next() != CharacterIterator.DONE);
  842                   if (iterator.current() != CharacterIterator.DONE) {
  843                       int limit = iterator.getRunLimit(field);
  844   
  845                       if (--count <= 0) {
  846                           getFormattedTextField().select(iterator.getIndex(),
  847                                                          limit);
  848                           break;
  849                       }
  850                       iterator.setIndex(limit);
  851                       iterator.next();
  852                   }
  853               }
  854           }
  855       }
  856   
  857       /**
  858        * Returns the field that will be adjusted by adjustValue.
  859        */
  860       Object getAdjustField(int start, Map attributes) {
  861           return null;
  862       }
  863   
  864       /**
  865        * Returns the number of occurences of <code>f</code> before
  866        * the location <code>start</code> in the current
  867        * <code>AttributedCharacterIterator</code>.
  868        */
  869       private int getFieldTypeCountTo(Object f, int start) {
  870           AttributedCharacterIterator iterator = getIterator();
  871           int count = 0;
  872   
  873           if (iterator != null &&
  874                       (f instanceof AttributedCharacterIterator.Attribute)) {
  875               AttributedCharacterIterator.Attribute field =
  876                                      (AttributedCharacterIterator.Attribute)f;
  877   
  878               iterator.first();
  879               while (iterator.getIndex() < start) {
  880                   while (iterator.getAttribute(field) == null &&
  881                          iterator.next() != CharacterIterator.DONE);
  882                   if (iterator.current() != CharacterIterator.DONE) {
  883                       iterator.setIndex(iterator.getRunLimit(field));
  884                       iterator.next();
  885                       count++;
  886                   }
  887                   else {
  888                       break;
  889                   }
  890               }
  891           }
  892           return count;
  893       }
  894   
  895       /**
  896        * Subclasses supporting incrementing must override this to handle
  897        * the actual incrementing. <code>value</code> is the current value,
  898        * <code>attributes</code> gives the field the cursor is in (may be
  899        * null depending upon <code>canIncrement</code>) and
  900        * <code>direction</code> is the amount to increment by.
  901        */
  902       Object adjustValue(Object value, Map attributes, Object field,
  903                              int direction) throws
  904                         BadLocationException, ParseException {
  905           return null;
  906       }
  907   
  908       /**
  909        * Returns false, indicating InternationalFormatter does not allow
  910        * incrementing of the value. Subclasses that wish to support
  911        * incrementing/decrementing the value should override this and
  912        * return true. Subclasses should also override
  913        * <code>adjustValue</code>.
  914        */
  915       boolean getSupportsIncrement() {
  916           return false;
  917       }
  918   
  919       /**
  920        * Resets the value of the JFormattedTextField to be
  921        * <code>value</code>.
  922        */
  923       void resetValue(Object value) throws BadLocationException, ParseException {
  924           Document doc = getFormattedTextField().getDocument();
  925           String string = valueToString(value);
  926   
  927           try {
  928               ignoreDocumentMutate = true;
  929               doc.remove(0, doc.getLength());
  930               doc.insertString(0, string, null);
  931           } finally {
  932               ignoreDocumentMutate = false;
  933           }
  934           updateValue(value);
  935       }
  936   
  937       /**
  938        * Subclassed to update the internal representation of the mask after
  939        * the default read operation has completed.
  940        */
  941       private void readObject(ObjectInputStream s)
  942           throws IOException, ClassNotFoundException {
  943           s.defaultReadObject();
  944           updateMaskIfNecessary();
  945       }
  946   
  947   
  948       /**
  949        * Overriden to return an instance of <code>ExtendedReplaceHolder</code>.
  950        */
  951       ReplaceHolder getReplaceHolder(DocumentFilter.FilterBypass fb, int offset,
  952                                      int length, String text,
  953                                      AttributeSet attrs) {
  954           if (replaceHolder == null) {
  955               replaceHolder = new ExtendedReplaceHolder();
  956           }
  957           return super.getReplaceHolder(fb, offset, length, text, attrs);
  958       }
  959   
  960   
  961       /**
  962        * As InternationalFormatter replaces the complete text on every edit,
  963        * ExtendedReplaceHolder keeps track of the offset and length passed
  964        * into canReplace.
  965        */
  966       static class ExtendedReplaceHolder extends ReplaceHolder {
  967           /** Offset of the insert/remove. This may differ from offset in
  968            * that if !allowsInvalid the text is replaced on every edit. */
  969           int endOffset;
  970           /** Length of the text. This may differ from text.length in
  971            * that if !allowsInvalid the text is replaced on every edit. */
  972           int endTextLength;
  973   
  974           /**
  975            * Resets the region to delete to be the complete document and
  976            * the text from invoking valueToString on the current value.
  977            */
  978           void resetFromValue(InternationalFormatter formatter) {
  979               // Need to reset the complete string as Format's result can
  980               // be completely different.
  981               offset = 0;
  982               try {
  983                   text = formatter.valueToString(value);
  984               } catch (ParseException pe) {
  985                   // Should never happen, otherwise canReplace would have
  986                   // returned value.
  987                   text = "";
  988               }
  989               length = fb.getDocument().getLength();
  990           }
  991       }
  992   
  993   
  994       /**
  995        * IncrementAction is used to increment the value by a certain amount.
  996        * It calls into <code>adjustValue</code> to handle the actual
  997        * incrementing of the value.
  998        */
  999       private class IncrementAction extends AbstractAction {
 1000           private int direction;
 1001   
 1002           IncrementAction(String name, int direction) {
 1003               super(name);
 1004               this.direction = direction;
 1005           }
 1006   
 1007           public void actionPerformed(ActionEvent ae) {
 1008   
 1009               if (getFormattedTextField().isEditable()) {
 1010                   if (getAllowsInvalid()) {
 1011                       // This will work if the currently edited value is valid.
 1012                       updateMask();
 1013                   }
 1014   
 1015                   boolean validEdit = false;
 1016   
 1017                   if (isValidMask()) {
 1018                       int start = getFormattedTextField().getSelectionStart();
 1019   
 1020                       if (start != -1) {
 1021                           AttributedCharacterIterator iterator = getIterator();
 1022   
 1023                           iterator.setIndex(start);
 1024   
 1025                           Map attributes = iterator.getAttributes();
 1026                           Object field = getAdjustField(start, attributes);
 1027   
 1028                           if (canIncrement(field, start)) {
 1029                               try {
 1030                                   Object value = stringToValue(
 1031                                           getFormattedTextField().getText());
 1032                                   int fieldTypeCount = getFieldTypeCountTo(
 1033                                           field, start);
 1034   
 1035                                   value = adjustValue(value, attributes,
 1036                                           field, direction);
 1037                                   if (value != null && isValidValue(value, false)) {
 1038                                       resetValue(value);
 1039                                       updateMask();
 1040   
 1041                                       if (isValidMask()) {
 1042                                           selectField(field, fieldTypeCount);
 1043                                       }
 1044                                       validEdit = true;
 1045                                   }
 1046                               }
 1047                               catch (ParseException pe) { }
 1048                               catch (BadLocationException ble) { }
 1049                           }
 1050                       }
 1051                   }
 1052                   if (!validEdit) {
 1053                       invalidEdit();
 1054                   }
 1055               }
 1056           }
 1057       }
 1058   }

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