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.awt.event.ActionEvent;
0N/Aimport java.io.*;
0N/Aimport java.text.*;
611N/Aimport java.text.AttributedCharacterIterator.Attribute;
0N/Aimport java.util.*;
0N/Aimport javax.swing.*;
0N/A
0N/A/**
0N/A * <code>InternationalFormatter</code> extends <code>DefaultFormatter</code>,
0N/A * using an instance of <code>java.text.Format</code> to handle the
0N/A * conversion to a String, and the conversion from a String.
0N/A * <p>
0N/A * If <code>getAllowsInvalid()</code> is false, this will ask the
0N/A * <code>Format</code> to format the current text on every edit.
0N/A * <p>
0N/A * You can specify a minimum and maximum value by way of the
0N/A * <code>setMinimum</code> and <code>setMaximum</code> methods. In order
0N/A * for this to work the values returned from <code>stringToValue</code> must be
0N/A * comparable to the min/max values by way of the <code>Comparable</code>
0N/A * interface.
0N/A * <p>
0N/A * Be careful how you configure the <code>Format</code> and the
0N/A * <code>InternationalFormatter</code>, as it is possible to create a
0N/A * situation where certain values can not be input. Consider the date
0N/A * format 'M/d/yy', an <code>InternationalFormatter</code> that is always
0N/A * valid (<code>setAllowsInvalid(false)</code>), is in overwrite mode
0N/A * (<code>setOverwriteMode(true)</code>) and the date 7/1/99. In this
0N/A * case the user will not be able to enter a two digit month or day of
0N/A * month. To avoid this, the format should be 'MM/dd/yy'.
0N/A * <p>
0N/A * If <code>InternationalFormatter</code> is configured to only allow valid
0N/A * values (<code>setAllowsInvalid(false)</code>), every valid edit will result
0N/A * in the text of the <code>JFormattedTextField</code> being completely reset
0N/A * from the <code>Format</code>.
0N/A * The cursor position will also be adjusted as literal characters are
0N/A * added/removed from the resulting String.
0N/A * <p>
0N/A * <code>InternationalFormatter</code>'s behavior of
0N/A * <code>stringToValue</code> is slightly different than that of
0N/A * <code>DefaultTextFormatter</code>, it does the following:
0N/A * <ol>
0N/A * <li><code>parseObject</code> is invoked on the <code>Format</code>
0N/A * specified by <code>setFormat</code>
0N/A * <li>If a Class has been set for the values (<code>setValueClass</code>),
0N/A * supers implementation is invoked to convert the value returned
0N/A * from <code>parseObject</code> to the appropriate class.
0N/A * <li>If a <code>ParseException</code> has not been thrown, and the value
0N/A * is outside the min/max a <code>ParseException</code> is thrown.
0N/A * <li>The value is returned.
0N/A * </ol>
0N/A * <code>InternationalFormatter</code> implements <code>stringToValue</code>
0N/A * in this manner so that you can specify an alternate Class than
0N/A * <code>Format</code> may return.
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 * @see java.text.Format
0N/A * @see java.lang.Comparable
0N/A *
0N/A * @since 1.4
0N/A */
0N/Apublic class InternationalFormatter extends DefaultFormatter {
0N/A /**
0N/A * Used by <code>getFields</code>.
0N/A */
0N/A private static final Format.Field[] EMPTY_FIELD_ARRAY =new Format.Field[0];
0N/A
0N/A /**
0N/A * Object used to handle the conversion.
0N/A */
0N/A private Format format;
0N/A /**
0N/A * Can be used to impose a maximum value.
0N/A */
0N/A private Comparable max;
0N/A /**
0N/A * Can be used to impose a minimum value.
0N/A */
0N/A private Comparable min;
0N/A
0N/A /**
0N/A * <code>InternationalFormatter</code>'s behavior is dicatated by a
0N/A * <code>AttributedCharacterIterator</code> that is obtained from
0N/A * the <code>Format</code>. On every edit, assuming
0N/A * allows invalid is false, the <code>Format</code> instance is invoked
0N/A * with <code>formatToCharacterIterator</code>. A <code>BitSet</code> is
0N/A * also kept upto date with the non-literal characters, that is
0N/A * for every index in the <code>AttributedCharacterIterator</code> an
0N/A * entry in the bit set is updated based on the return value from
0N/A * <code>isLiteral(Map)</code>. <code>isLiteral(int)</code> then uses
0N/A * this cached information.
0N/A * <p>
0N/A * If allowsInvalid is false, every edit results in resetting the complete
0N/A * text of the JTextComponent.
0N/A * <p>
0N/A * InternationalFormatterFilter can also provide two actions suitable for
0N/A * incrementing and decrementing. To enable this a subclass must
0N/A * override <code>getSupportsIncrement</code> to return true, and
0N/A * override <code>adjustValue</code> to handle the changing of the
0N/A * value. If you want to support changing the value outside of
0N/A * the valid FieldPositions, you will need to override
0N/A * <code>canIncrement</code>.
0N/A */
0N/A /**
0N/A * A bit is set for every index identified in the
0N/A * AttributedCharacterIterator that is not considered decoration.
0N/A * This should only be used if validMask is true.
0N/A */
0N/A private transient BitSet literalMask;
0N/A /**
0N/A * Used to iterate over characters.
0N/A */
0N/A private transient AttributedCharacterIterator iterator;
0N/A /**
0N/A * True if the Format was able to convert the value to a String and
0N/A * back.
0N/A */
0N/A private transient boolean validMask;
0N/A /**
0N/A * Current value being displayed.
0N/A */
0N/A private transient String string;
0N/A /**
0N/A * If true, DocumentFilter methods are unconditionally allowed,
0N/A * and no checking is done on their values. This is used when
0N/A * incrementing/decrementing via the actions.
0N/A */
0N/A private transient boolean ignoreDocumentMutate;
0N/A
0N/A
0N/A /**
0N/A * Creates an <code>InternationalFormatter</code> with no
0N/A * <code>Format</code> specified.
0N/A */
0N/A public InternationalFormatter() {
0N/A setOverwriteMode(false);
0N/A }
0N/A
0N/A /**
0N/A * Creates an <code>InternationalFormatter</code> with the specified
0N/A * <code>Format</code> instance.
0N/A *
0N/A * @param format Format instance used for converting from/to Strings
0N/A */
0N/A public InternationalFormatter(Format format) {
0N/A this();
0N/A setFormat(format);
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 *
0N/A * @param format <code>Format</code> instance used for converting
0N/A * from/to Strings
0N/A */
0N/A public void setFormat(Format format) {
0N/A this.format = format;
0N/A }
0N/A
0N/A /**
0N/A * Returns the format that dictates the legal values that can be edited
0N/A * and displayed.
0N/A *
0N/A * @return Format instance used for converting from/to Strings
0N/A */
0N/A public Format getFormat() {
0N/A return format;
0N/A }
0N/A
0N/A /**
0N/A * Sets the minimum permissible value. If the <code>valueClass</code> has
0N/A * not been specified, and <code>minimum</code> is non null, the
0N/A * <code>valueClass</code> will be set to that of the class of
0N/A * <code>minimum</code>.
0N/A *
0N/A * @param minimum Minimum legal value that can be input
0N/A * @see #setValueClass
0N/A */
0N/A public void setMinimum(Comparable minimum) {
0N/A if (getValueClass() == null && minimum != null) {
0N/A setValueClass(minimum.getClass());
0N/A }
0N/A min = minimum;
0N/A }
0N/A
0N/A /**
0N/A * Returns the minimum permissible value.
0N/A *
0N/A * @return Minimum legal value that can be input
0N/A */
0N/A public Comparable getMinimum() {
0N/A return min;
0N/A }
0N/A
0N/A /**
0N/A * Sets the maximum permissible value. If the <code>valueClass</code> has
0N/A * not been specified, and <code>max</code> is non null, the
0N/A * <code>valueClass</code> will be set to that of the class of
0N/A * <code>max</code>.
0N/A *
0N/A * @param max Maximum legal value that can be input
0N/A * @see #setValueClass
0N/A */
0N/A public void setMaximum(Comparable max) {
0N/A if (getValueClass() == null && max != null) {
0N/A setValueClass(max.getClass());
0N/A }
0N/A this.max = max;
0N/A }
0N/A
0N/A /**
0N/A * Returns the maximum permissible value.
0N/A *
0N/A * @return Maximum legal value that can be input
0N/A */
0N/A public Comparable getMaximum() {
0N/A return max;
0N/A }
0N/A
0N/A /**
0N/A * Installs the <code>DefaultFormatter</code> onto a particular
0N/A * <code>JFormattedTextField</code>.
0N/A * This will invoke <code>valueToString</code> to convert the
0N/A * current value from the <code>JFormattedTextField</code> to
0N/A * a String. This will then install the <code>Action</code>s from
0N/A * <code>getActions</code>, the <code>DocumentFilter</code>
0N/A * returned from <code>getDocumentFilter</code> and the
0N/A * <code>NavigationFilter</code> returned from
0N/A * <code>getNavigationFilter</code> onto the
0N/A * <code>JFormattedTextField</code>.
0N/A * <p>
0N/A * Subclasses will typically only need to override this if they
0N/A * wish to install additional listeners on the
0N/A * <code>JFormattedTextField</code>.
0N/A * <p>
0N/A * If there is a <code>ParseException</code> in converting the
0N/A * current value to a String, this will set the text to an empty
0N/A * String, and mark the <code>JFormattedTextField</code> as being
0N/A * in an invalid state.
0N/A * <p>
0N/A * While this is a public method, this is typically only useful
0N/A * for subclassers of <code>JFormattedTextField</code>.
0N/A * <code>JFormattedTextField</code> will invoke this method at
0N/A * the appropriate times when the value changes, or its internal
0N/A * state changes.
0N/A *
0N/A * @param ftf JFormattedTextField to format for, may be null indicating
0N/A * uninstall from current JFormattedTextField.
0N/A */
0N/A public void install(JFormattedTextField ftf) {
0N/A super.install(ftf);
0N/A updateMaskIfNecessary();
0N/A // invoked again as the mask should now be valid.
0N/A positionCursorAtInitialLocation();
0N/A }
0N/A
0N/A /**
0N/A * Returns a String representation of the Object <code>value</code>.
0N/A * This invokes <code>format</code> on the current <code>Format</code>.
0N/A *
0N/A * @throws ParseException if there is an error in the conversion
0N/A * @param value Value to convert
0N/A * @return String representation of value
0N/A */
0N/A public String valueToString(Object value) throws ParseException {
0N/A if (value == null) {
0N/A return "";
0N/A }
0N/A Format f = getFormat();
0N/A
0N/A if (f == null) {
0N/A return value.toString();
0N/A }
0N/A return f.format(value);
0N/A }
0N/A
0N/A /**
0N/A * Returns the <code>Object</code> representation of the
0N/A * <code>String</code> <code>text</code>.
0N/A *
0N/A * @param text <code>String</code> to convert
0N/A * @return <code>Object</code> representation of text
0N/A * @throws ParseException if there is an error in the conversion
0N/A */
0N/A public Object stringToValue(String text) throws ParseException {
0N/A Object value = stringToValue(text, getFormat());
0N/A
0N/A // Convert to the value class if the Value returned from the
0N/A // Format does not match.
0N/A if (value != null && getValueClass() != null &&
0N/A !getValueClass().isInstance(value)) {
0N/A value = super.stringToValue(value.toString());
0N/A }
0N/A try {
0N/A if (!isValidValue(value, true)) {
0N/A throw new ParseException("Value not within min/max range", 0);
0N/A }
0N/A } catch (ClassCastException cce) {
0N/A throw new ParseException("Class cast exception comparing values: "
0N/A + cce, 0);
0N/A }
0N/A return value;
0N/A }
0N/A
0N/A /**
0N/A * Returns the <code>Format.Field</code> constants associated with
0N/A * the text at <code>offset</code>. If <code>offset</code> is not
0N/A * a valid location into the current text, this will return an
0N/A * empty array.
0N/A *
0N/A * @param offset offset into text to be examined
0N/A * @return Format.Field constants associated with the text at the
0N/A * given position.
0N/A */
0N/A public Format.Field[] getFields(int offset) {
0N/A if (getAllowsInvalid()) {
0N/A // This will work if the currently edited value is valid.
0N/A updateMask();
0N/A }
0N/A
611N/A Map<Attribute, Object> attrs = getAttributes(offset);
0N/A
0N/A if (attrs != null && attrs.size() > 0) {
611N/A ArrayList<Attribute> al = new ArrayList<Attribute>();
0N/A
0N/A al.addAll(attrs.keySet());
611N/A return al.toArray(EMPTY_FIELD_ARRAY);
0N/A }
0N/A return EMPTY_FIELD_ARRAY;
0N/A }
0N/A
0N/A /**
0N/A * Creates a copy of the DefaultFormatter.
0N/A *
0N/A * @return copy of the DefaultFormatter
0N/A */
0N/A public Object clone() throws CloneNotSupportedException {
0N/A InternationalFormatter formatter = (InternationalFormatter)super.
0N/A clone();
0N/A
0N/A formatter.literalMask = null;
0N/A formatter.iterator = null;
0N/A formatter.validMask = false;
0N/A formatter.string = null;
0N/A return formatter;
0N/A }
0N/A
0N/A /**
0N/A * If <code>getSupportsIncrement</code> returns true, this returns
0N/A * two Actions suitable for incrementing/decrementing the value.
0N/A */
0N/A protected Action[] getActions() {
0N/A if (getSupportsIncrement()) {
0N/A return new Action[] { new IncrementAction("increment", 1),
0N/A new IncrementAction("decrement", -1) };
0N/A }
0N/A return null;
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 return f.parseObject(text);
0N/A }
0N/A
0N/A /**
0N/A * Returns true if <code>value</code> is between the min/max.
0N/A *
0N/A * @param wantsCCE If false, and a ClassCastException is thrown in
0N/A * comparing the values, the exception is consumed and
0N/A * false is returned.
0N/A */
0N/A boolean isValidValue(Object value, boolean wantsCCE) {
0N/A Comparable min = getMinimum();
0N/A
0N/A try {
0N/A if (min != null && min.compareTo(value) > 0) {
0N/A return false;
0N/A }
0N/A } catch (ClassCastException cce) {
0N/A if (wantsCCE) {
0N/A throw cce;
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A Comparable max = getMaximum();
0N/A try {
0N/A if (max != null && max.compareTo(value) < 0) {
0N/A return false;
0N/A }
0N/A } catch (ClassCastException cce) {
0N/A if (wantsCCE) {
0N/A throw cce;
0N/A }
0N/A return false;
0N/A }
0N/A return true;
0N/A }
0N/A
0N/A /**
0N/A * Returns a Set of the attribute identifiers at <code>index</code>.
0N/A */
611N/A Map<Attribute, Object> getAttributes(int index) {
0N/A if (isValidMask()) {
0N/A AttributedCharacterIterator iterator = getIterator();
0N/A
0N/A if (index >= 0 && index <= iterator.getEndIndex()) {
0N/A iterator.setIndex(index);
0N/A return iterator.getAttributes();
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Returns the start of the first run that contains the attribute
0N/A * <code>id</code>. This will return <code>-1</code> if the attribute
0N/A * can not be found.
0N/A */
0N/A int getAttributeStart(AttributedCharacterIterator.Attribute id) {
0N/A if (isValidMask()) {
0N/A AttributedCharacterIterator iterator = getIterator();
0N/A
0N/A iterator.first();
0N/A while (iterator.current() != CharacterIterator.DONE) {
0N/A if (iterator.getAttribute(id) != null) {
0N/A return iterator.getIndex();
0N/A }
0N/A iterator.next();
0N/A }
0N/A }
0N/A return -1;
0N/A }
0N/A
0N/A /**
0N/A * Returns the <code>AttributedCharacterIterator</code> used to
0N/A * format the last value.
0N/A */
0N/A AttributedCharacterIterator getIterator() {
0N/A return iterator;
0N/A }
0N/A
0N/A /**
0N/A * Updates the AttributedCharacterIterator and bitset, if necessary.
0N/A */
0N/A void updateMaskIfNecessary() {
0N/A if (!getAllowsInvalid() && (getFormat() != null)) {
0N/A if (!isValidMask()) {
0N/A updateMask();
0N/A }
0N/A else {
0N/A String newString = getFormattedTextField().getText();
0N/A
0N/A if (!newString.equals(string)) {
0N/A updateMask();
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Updates the AttributedCharacterIterator by invoking
0N/A * <code>formatToCharacterIterator</code> on the <code>Format</code>.
0N/A * If this is successful,
0N/A * <code>updateMask(AttributedCharacterIterator)</code>
0N/A * is then invoked to update the internal bitmask.
0N/A */
0N/A void updateMask() {
0N/A if (getFormat() != null) {
0N/A Document doc = getFormattedTextField().getDocument();
0N/A
0N/A validMask = false;
0N/A if (doc != null) {
0N/A try {
0N/A string = doc.getText(0, doc.getLength());
0N/A } catch (BadLocationException ble) {
0N/A string = null;
0N/A }
0N/A if (string != null) {
0N/A try {
0N/A Object value = stringToValue(string);
0N/A AttributedCharacterIterator iterator = getFormat().
0N/A formatToCharacterIterator(value);
0N/A
0N/A updateMask(iterator);
0N/A }
0N/A catch (ParseException pe) {}
0N/A catch (IllegalArgumentException iae) {}
0N/A catch (NullPointerException npe) {}
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Returns the number of literal characters before <code>index</code>.
0N/A */
0N/A int getLiteralCountTo(int index) {
0N/A int lCount = 0;
0N/A
0N/A for (int counter = 0; counter < index; counter++) {
0N/A if (isLiteral(counter)) {
0N/A lCount++;
0N/A }
0N/A }
0N/A return lCount;
0N/A }
0N/A
0N/A /**
0N/A * Returns true if the character at index is a literal, that is
0N/A * not editable.
0N/A */
0N/A boolean isLiteral(int index) {
0N/A if (isValidMask() && index < string.length()) {
0N/A return literalMask.get(index);
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A /**
0N/A * Returns the literal character at index.
0N/A */
0N/A char getLiteral(int index) {
0N/A if (isValidMask() && string != null && index < string.length()) {
0N/A return string.charAt(index);
0N/A }
0N/A return (char)0;
0N/A }
0N/A
0N/A /**
0N/A * Returns true if the character at offset is navigatable too. This
0N/A * is implemented in terms of <code>isLiteral</code>, subclasses
0N/A * may wish to provide different behavior.
0N/A */
0N/A boolean isNavigatable(int offset) {
0N/A return !isLiteral(offset);
0N/A }
0N/A
0N/A /**
0N/A * Overriden to update the mask after invoking supers implementation.
0N/A */
0N/A void updateValue(Object value) {
0N/A super.updateValue(value);
0N/A updateMaskIfNecessary();
0N/A }
0N/A
0N/A /**
0N/A * Overriden to unconditionally allow the replace if
0N/A * ignoreDocumentMutate is true.
0N/A */
0N/A void replace(DocumentFilter.FilterBypass fb, int offset,
0N/A int length, String text,
0N/A AttributeSet attrs) throws BadLocationException {
0N/A if (ignoreDocumentMutate) {
0N/A fb.replace(offset, length, text, attrs);
0N/A return;
0N/A }
0N/A super.replace(fb, offset, length, text, attrs);
0N/A }
0N/A
0N/A /**
0N/A * Returns the index of the next non-literal character starting at
0N/A * index. If index is not a literal, it will be returned.
0N/A *
0N/A * @param direction Amount to increment looking for non-literal
0N/A */
0N/A private int getNextNonliteralIndex(int index, int direction) {
0N/A int max = getFormattedTextField().getDocument().getLength();
0N/A
0N/A while (index >= 0 && index < max) {
0N/A if (isLiteral(index)) {
0N/A index += direction;
0N/A }
0N/A else {
0N/A return index;
0N/A }
0N/A }
0N/A return (direction == -1) ? 0 : max;
0N/A }
0N/A
0N/A /**
0N/A * Overriden in an attempt to honor the literals.
2599N/A * <p>If we do not allow invalid values and are in overwrite mode, this
2599N/A * {@code rh.length} is corrected as to preserve trailing literals.
0N/A * If not in overwrite mode, and there is text to insert it is
0N/A * inserted at the next non literal index going forward. If there
0N/A * is only text to remove, it is removed from the next non literal
0N/A * index going backward.
0N/A */
0N/A boolean canReplace(ReplaceHolder rh) {
0N/A if (!getAllowsInvalid()) {
0N/A String text = rh.text;
0N/A int tl = (text != null) ? text.length() : 0;
2599N/A JTextComponent c = getFormattedTextField();
0N/A
2599N/A if (tl == 0 && rh.length == 1 && c.getSelectionStart() != rh.offset) {
0N/A // Backspace, adjust to actually delete next non-literal.
0N/A rh.offset = getNextNonliteralIndex(rh.offset, -1);
2599N/A } else if (getOverwriteMode()) {
2599N/A int pos = rh.offset;
2599N/A int textPos = pos;
2599N/A boolean overflown = false;
0N/A
2599N/A for (int i = 0; i < rh.length; i++) {
2599N/A while (isLiteral(pos)) pos++;
2599N/A if (pos >= string.length()) {
2599N/A pos = textPos;
2599N/A overflown = true;
2599N/A break;
0N/A }
2599N/A textPos = ++pos;
0N/A }
2599N/A if (overflown || c.getSelectedText() == null) {
2599N/A rh.length = pos - rh.offset;
0N/A }
0N/A }
0N/A else if (tl > 0) {
0N/A // insert (or insert and remove)
0N/A rh.offset = getNextNonliteralIndex(rh.offset, 1);
0N/A }
0N/A else {
0N/A // remove only
0N/A rh.offset = getNextNonliteralIndex(rh.offset, -1);
0N/A }
0N/A ((ExtendedReplaceHolder)rh).endOffset = rh.offset;
0N/A ((ExtendedReplaceHolder)rh).endTextLength = (rh.text != null) ?
0N/A rh.text.length() : 0;
0N/A }
0N/A else {
0N/A ((ExtendedReplaceHolder)rh).endOffset = rh.offset;
0N/A ((ExtendedReplaceHolder)rh).endTextLength = (rh.text != null) ?
0N/A rh.text.length() : 0;
0N/A }
0N/A boolean can = super.canReplace(rh);
0N/A if (can && !getAllowsInvalid()) {
0N/A ((ExtendedReplaceHolder)rh).resetFromValue(this);
0N/A }
0N/A return can;
0N/A }
0N/A
0N/A /**
0N/A * When in !allowsInvalid mode the text is reset on every edit, thus
0N/A * supers implementation will position the cursor at the wrong position.
0N/A * As such, this invokes supers implementation and then invokes
0N/A * <code>repositionCursor</code> to correctly reset the cursor.
0N/A */
0N/A boolean replace(ReplaceHolder rh) throws BadLocationException {
0N/A int start = -1;
0N/A int direction = 1;
0N/A int literalCount = -1;
0N/A
0N/A if (rh.length > 0 && (rh.text == null || rh.text.length() == 0) &&
0N/A (getFormattedTextField().getSelectionStart() != rh.offset ||
0N/A rh.length > 1)) {
0N/A direction = -1;
0N/A }
0N/A if (!getAllowsInvalid()) {
0N/A if ((rh.text == null || rh.text.length() == 0) && rh.length > 0) {
0N/A // remove
0N/A start = getFormattedTextField().getSelectionStart();
0N/A }
0N/A else {
0N/A start = rh.offset;
0N/A }
0N/A literalCount = getLiteralCountTo(start);
0N/A }
0N/A if (super.replace(rh)) {
0N/A if (start != -1) {
0N/A int end = ((ExtendedReplaceHolder)rh).endOffset;
0N/A
0N/A end += ((ExtendedReplaceHolder)rh).endTextLength;
0N/A repositionCursor(literalCount, end, direction);
0N/A }
0N/A else {
0N/A start = ((ExtendedReplaceHolder)rh).endOffset;
0N/A if (direction == 1) {
0N/A start += ((ExtendedReplaceHolder)rh).endTextLength;
0N/A }
0N/A repositionCursor(start, direction);
0N/A }
0N/A return true;
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A /**
0N/A * Repositions the cursor. <code>startLiteralCount</code> gives
0N/A * the number of literals to the start of the deleted range, end
0N/A * gives the ending location to adjust from, direction gives
0N/A * the direction relative to <code>end</code> to position the
0N/A * cursor from.
0N/A */
0N/A private void repositionCursor(int startLiteralCount, int end,
0N/A int direction) {
0N/A int endLiteralCount = getLiteralCountTo(end);
0N/A
0N/A if (endLiteralCount != end) {
0N/A end -= startLiteralCount;
0N/A for (int counter = 0; counter < end; counter++) {
0N/A if (isLiteral(counter)) {
0N/A end++;
0N/A }
0N/A }
0N/A }
0N/A repositionCursor(end, 1 /*direction*/);
0N/A }
0N/A
0N/A /**
0N/A * Returns the character from the mask that has been buffered
0N/A * at <code>index</code>.
0N/A */
0N/A char getBufferedChar(int index) {
0N/A if (isValidMask()) {
0N/A if (string != null && index < string.length()) {
0N/A return string.charAt(index);
0N/A }
0N/A }
0N/A return (char)0;
0N/A }
0N/A
0N/A /**
0N/A * Returns true if the current mask is valid.
0N/A */
0N/A boolean isValidMask() {
0N/A return validMask;
0N/A }
0N/A
0N/A /**
0N/A * Returns true if <code>attributes</code> is null or empty.
0N/A */
0N/A boolean isLiteral(Map attributes) {
0N/A return ((attributes == null) || attributes.size() == 0);
0N/A }
0N/A
0N/A /**
0N/A * Updates the interal bitset from <code>iterator</code>. This will
0N/A * set <code>validMask</code> to true if <code>iterator</code> is
0N/A * non-null.
0N/A */
0N/A private void updateMask(AttributedCharacterIterator iterator) {
0N/A if (iterator != null) {
0N/A validMask = true;
0N/A this.iterator = iterator;
0N/A
0N/A // Update the literal mask
0N/A if (literalMask == null) {
0N/A literalMask = new BitSet();
0N/A }
0N/A else {
0N/A for (int counter = literalMask.length() - 1; counter >= 0;
0N/A counter--) {
0N/A literalMask.clear(counter);
0N/A }
0N/A }
0N/A
0N/A iterator.first();
0N/A while (iterator.current() != CharacterIterator.DONE) {
0N/A Map attributes = iterator.getAttributes();
0N/A boolean set = isLiteral(attributes);
0N/A int start = iterator.getIndex();
0N/A int end = iterator.getRunLimit();
0N/A
0N/A while (start < end) {
0N/A if (set) {
0N/A literalMask.set(start);
0N/A }
0N/A else {
0N/A literalMask.clear(start);
0N/A }
0N/A start++;
0N/A }
0N/A iterator.setIndex(start);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Returns true if <code>field</code> is non-null.
0N/A * Subclasses that wish to allow incrementing to happen outside of
0N/A * the known fields will need to override this.
0N/A */
0N/A boolean canIncrement(Object field, int cursorPosition) {
0N/A return (field != null);
0N/A }
0N/A
0N/A /**
0N/A * Selects the fields identified by <code>attributes</code>.
0N/A */
0N/A void selectField(Object f, int count) {
0N/A AttributedCharacterIterator iterator = getIterator();
0N/A
0N/A if (iterator != null &&
0N/A (f instanceof AttributedCharacterIterator.Attribute)) {
0N/A AttributedCharacterIterator.Attribute field =
0N/A (AttributedCharacterIterator.Attribute)f;
0N/A
0N/A iterator.first();
0N/A while (iterator.current() != CharacterIterator.DONE) {
0N/A while (iterator.getAttribute(field) == null &&
0N/A iterator.next() != CharacterIterator.DONE);
0N/A if (iterator.current() != CharacterIterator.DONE) {
0N/A int limit = iterator.getRunLimit(field);
0N/A
0N/A if (--count <= 0) {
0N/A getFormattedTextField().select(iterator.getIndex(),
0N/A limit);
0N/A break;
0N/A }
0N/A iterator.setIndex(limit);
0N/A iterator.next();
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Returns the field that will be adjusted by adjustValue.
0N/A */
0N/A Object getAdjustField(int start, Map attributes) {
0N/A return null;
0N/A }
0N/A
0N/A /**
0N/A * Returns the number of occurences of <code>f</code> before
0N/A * the location <code>start</code> in the current
0N/A * <code>AttributedCharacterIterator</code>.
0N/A */
0N/A private int getFieldTypeCountTo(Object f, int start) {
0N/A AttributedCharacterIterator iterator = getIterator();
0N/A int count = 0;
0N/A
0N/A if (iterator != null &&
0N/A (f instanceof AttributedCharacterIterator.Attribute)) {
0N/A AttributedCharacterIterator.Attribute field =
0N/A (AttributedCharacterIterator.Attribute)f;
0N/A
0N/A iterator.first();
0N/A while (iterator.getIndex() < start) {
0N/A while (iterator.getAttribute(field) == null &&
0N/A iterator.next() != CharacterIterator.DONE);
0N/A if (iterator.current() != CharacterIterator.DONE) {
0N/A iterator.setIndex(iterator.getRunLimit(field));
0N/A iterator.next();
0N/A count++;
0N/A }
0N/A else {
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A return count;
0N/A }
0N/A
0N/A /**
0N/A * Subclasses supporting incrementing must override this to handle
0N/A * the actual incrementing. <code>value</code> is the current value,
0N/A * <code>attributes</code> gives the field the cursor is in (may be
0N/A * null depending upon <code>canIncrement</code>) and
0N/A * <code>direction</code> is the amount to increment by.
0N/A */
0N/A Object adjustValue(Object value, Map attributes, Object field,
0N/A int direction) throws
0N/A BadLocationException, ParseException {
0N/A return null;
0N/A }
0N/A
0N/A /**
0N/A * Returns false, indicating InternationalFormatter does not allow
0N/A * incrementing of the value. Subclasses that wish to support
0N/A * incrementing/decrementing the value should override this and
0N/A * return true. Subclasses should also override
0N/A * <code>adjustValue</code>.
0N/A */
0N/A boolean getSupportsIncrement() {
0N/A return false;
0N/A }
0N/A
0N/A /**
0N/A * Resets the value of the JFormattedTextField to be
0N/A * <code>value</code>.
0N/A */
0N/A void resetValue(Object value) throws BadLocationException, ParseException {
0N/A Document doc = getFormattedTextField().getDocument();
0N/A String string = valueToString(value);
0N/A
0N/A try {
0N/A ignoreDocumentMutate = true;
0N/A doc.remove(0, doc.getLength());
0N/A doc.insertString(0, string, null);
0N/A } finally {
0N/A ignoreDocumentMutate = false;
0N/A }
0N/A updateValue(value);
0N/A }
0N/A
0N/A /**
0N/A * Subclassed to update the internal representation of the mask after
0N/A * the default read operation has completed.
0N/A */
0N/A private void readObject(ObjectInputStream s)
0N/A throws IOException, ClassNotFoundException {
0N/A s.defaultReadObject();
0N/A updateMaskIfNecessary();
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Overriden to return an instance of <code>ExtendedReplaceHolder</code>.
0N/A */
0N/A ReplaceHolder getReplaceHolder(DocumentFilter.FilterBypass fb, int offset,
0N/A int length, String text,
0N/A AttributeSet attrs) {
0N/A if (replaceHolder == null) {
0N/A replaceHolder = new ExtendedReplaceHolder();
0N/A }
0N/A return super.getReplaceHolder(fb, offset, length, text, attrs);
0N/A }
0N/A
0N/A
0N/A /**
0N/A * As InternationalFormatter replaces the complete text on every edit,
0N/A * ExtendedReplaceHolder keeps track of the offset and length passed
0N/A * into canReplace.
0N/A */
0N/A static class ExtendedReplaceHolder extends ReplaceHolder {
0N/A /** Offset of the insert/remove. This may differ from offset in
0N/A * that if !allowsInvalid the text is replaced on every edit. */
0N/A int endOffset;
0N/A /** Length of the text. This may differ from text.length in
0N/A * that if !allowsInvalid the text is replaced on every edit. */
0N/A int endTextLength;
0N/A
0N/A /**
0N/A * Resets the region to delete to be the complete document and
0N/A * the text from invoking valueToString on the current value.
0N/A */
0N/A void resetFromValue(InternationalFormatter formatter) {
0N/A // Need to reset the complete string as Format's result can
0N/A // be completely different.
0N/A offset = 0;
0N/A try {
0N/A text = formatter.valueToString(value);
0N/A } catch (ParseException pe) {
0N/A // Should never happen, otherwise canReplace would have
0N/A // returned value.
0N/A text = "";
0N/A }
0N/A length = fb.getDocument().getLength();
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * IncrementAction is used to increment the value by a certain amount.
0N/A * It calls into <code>adjustValue</code> to handle the actual
0N/A * incrementing of the value.
0N/A */
0N/A private class IncrementAction extends AbstractAction {
0N/A private int direction;
0N/A
0N/A IncrementAction(String name, int direction) {
0N/A super(name);
0N/A this.direction = direction;
0N/A }
0N/A
0N/A public void actionPerformed(ActionEvent ae) {
0N/A
0N/A if (getFormattedTextField().isEditable()) {
0N/A if (getAllowsInvalid()) {
0N/A // This will work if the currently edited value is valid.
0N/A updateMask();
0N/A }
0N/A
0N/A boolean validEdit = false;
0N/A
0N/A if (isValidMask()) {
0N/A int start = getFormattedTextField().getSelectionStart();
0N/A
0N/A if (start != -1) {
0N/A AttributedCharacterIterator iterator = getIterator();
0N/A
0N/A iterator.setIndex(start);
0N/A
0N/A Map attributes = iterator.getAttributes();
0N/A Object field = getAdjustField(start, attributes);
0N/A
0N/A if (canIncrement(field, start)) {
0N/A try {
0N/A Object value = stringToValue(
0N/A getFormattedTextField().getText());
0N/A int fieldTypeCount = getFieldTypeCountTo(
0N/A field, start);
0N/A
0N/A value = adjustValue(value, attributes,
0N/A field, direction);
0N/A if (value != null && isValidValue(value, false)) {
0N/A resetValue(value);
0N/A updateMask();
0N/A
0N/A if (isValidMask()) {
0N/A selectField(field, fieldTypeCount);
0N/A }
0N/A validEdit = true;
0N/A }
0N/A }
0N/A catch (ParseException pe) { }
0N/A catch (BadLocationException ble) { }
0N/A }
0N/A }
0N/A }
0N/A if (!validEdit) {
0N/A invalidEdit();
0N/A }
0N/A }
0N/A }
0N/A }
0N/A}