0001 /*
0002 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing.text;
0026
0027 import java.awt.event.ActionEvent;
0028 import java.io.*;
0029 import java.text.*;
0030 import java.util.*;
0031 import javax.swing.*;
0032 import javax.swing.text.*;
0033
0034 /**
0035 * <code>InternationalFormatter</code> extends <code>DefaultFormatter</code>,
0036 * using an instance of <code>java.text.Format</code> to handle the
0037 * conversion to a String, and the conversion from a String.
0038 * <p>
0039 * If <code>getAllowsInvalid()</code> is false, this will ask the
0040 * <code>Format</code> to format the current text on every edit.
0041 * <p>
0042 * You can specify a minimum and maximum value by way of the
0043 * <code>setMinimum</code> and <code>setMaximum</code> methods. In order
0044 * for this to work the values returned from <code>stringToValue</code> must be
0045 * comparable to the min/max values by way of the <code>Comparable</code>
0046 * interface.
0047 * <p>
0048 * Be careful how you configure the <code>Format</code> and the
0049 * <code>InternationalFormatter</code>, as it is possible to create a
0050 * situation where certain values can not be input. Consider the date
0051 * format 'M/d/yy', an <code>InternationalFormatter</code> that is always
0052 * valid (<code>setAllowsInvalid(false)</code>), is in overwrite mode
0053 * (<code>setOverwriteMode(true)</code>) and the date 7/1/99. In this
0054 * case the user will not be able to enter a two digit month or day of
0055 * month. To avoid this, the format should be 'MM/dd/yy'.
0056 * <p>
0057 * If <code>InternationalFormatter</code> is configured to only allow valid
0058 * values (<code>setAllowsInvalid(false)</code>), every valid edit will result
0059 * in the text of the <code>JFormattedTextField</code> being completely reset
0060 * from the <code>Format</code>.
0061 * The cursor position will also be adjusted as literal characters are
0062 * added/removed from the resulting String.
0063 * <p>
0064 * <code>InternationalFormatter</code>'s behavior of
0065 * <code>stringToValue</code> is slightly different than that of
0066 * <code>DefaultTextFormatter</code>, it does the following:
0067 * <ol>
0068 * <li><code>parseObject</code> is invoked on the <code>Format</code>
0069 * specified by <code>setFormat</code>
0070 * <li>If a Class has been set for the values (<code>setValueClass</code>),
0071 * supers implementation is invoked to convert the value returned
0072 * from <code>parseObject</code> to the appropriate class.
0073 * <li>If a <code>ParseException</code> has not been thrown, and the value
0074 * is outside the min/max a <code>ParseException</code> is thrown.
0075 * <li>The value is returned.
0076 * </ol>
0077 * <code>InternationalFormatter</code> implements <code>stringToValue</code>
0078 * in this manner so that you can specify an alternate Class than
0079 * <code>Format</code> may return.
0080 * <p>
0081 * <strong>Warning:</strong>
0082 * Serialized objects of this class will not be compatible with
0083 * future Swing releases. The current serialization support is
0084 * appropriate for short term storage or RMI between applications running
0085 * the same version of Swing. As of 1.4, support for long term storage
0086 * of all JavaBeans<sup><font size="-2">TM</font></sup>
0087 * has been added to the <code>java.beans</code> package.
0088 * Please see {@link java.beans.XMLEncoder}.
0089 *
0090 * @see java.text.Format
0091 * @see java.lang.Comparable
0092 *
0093 * @version 1.7 04/09/01
0094 * @since 1.4
0095 */
0096 public class InternationalFormatter extends DefaultFormatter {
0097 /**
0098 * Used by <code>getFields</code>.
0099 */
0100 private static final Format.Field[] EMPTY_FIELD_ARRAY = new Format.Field[0];
0101
0102 /**
0103 * Object used to handle the conversion.
0104 */
0105 private Format format;
0106 /**
0107 * Can be used to impose a maximum value.
0108 */
0109 private Comparable max;
0110 /**
0111 * Can be used to impose a minimum value.
0112 */
0113 private Comparable min;
0114
0115 /**
0116 * <code>InternationalFormatter</code>'s behavior is dicatated by a
0117 * <code>AttributedCharacterIterator</code> that is obtained from
0118 * the <code>Format</code>. On every edit, assuming
0119 * allows invalid is false, the <code>Format</code> instance is invoked
0120 * with <code>formatToCharacterIterator</code>. A <code>BitSet</code> is
0121 * also kept upto date with the non-literal characters, that is
0122 * for every index in the <code>AttributedCharacterIterator</code> an
0123 * entry in the bit set is updated based on the return value from
0124 * <code>isLiteral(Map)</code>. <code>isLiteral(int)</code> then uses
0125 * this cached information.
0126 * <p>
0127 * If allowsInvalid is false, every edit results in resetting the complete
0128 * text of the JTextComponent.
0129 * <p>
0130 * InternationalFormatterFilter can also provide two actions suitable for
0131 * incrementing and decrementing. To enable this a subclass must
0132 * override <code>getSupportsIncrement</code> to return true, and
0133 * override <code>adjustValue</code> to handle the changing of the
0134 * value. If you want to support changing the value outside of
0135 * the valid FieldPositions, you will need to override
0136 * <code>canIncrement</code>.
0137 */
0138 /**
0139 * A bit is set for every index identified in the
0140 * AttributedCharacterIterator that is not considered decoration.
0141 * This should only be used if validMask is true.
0142 */
0143 private transient BitSet literalMask;
0144 /**
0145 * Used to iterate over characters.
0146 */
0147 private transient AttributedCharacterIterator iterator;
0148 /**
0149 * True if the Format was able to convert the value to a String and
0150 * back.
0151 */
0152 private transient boolean validMask;
0153 /**
0154 * Current value being displayed.
0155 */
0156 private transient String string;
0157 /**
0158 * If true, DocumentFilter methods are unconditionally allowed,
0159 * and no checking is done on their values. This is used when
0160 * incrementing/decrementing via the actions.
0161 */
0162 private transient boolean ignoreDocumentMutate;
0163
0164 /**
0165 * Creates an <code>InternationalFormatter</code> with no
0166 * <code>Format</code> specified.
0167 */
0168 public InternationalFormatter() {
0169 setOverwriteMode(false);
0170 }
0171
0172 /**
0173 * Creates an <code>InternationalFormatter</code> with the specified
0174 * <code>Format</code> instance.
0175 *
0176 * @param format Format instance used for converting from/to Strings
0177 */
0178 public InternationalFormatter(Format format) {
0179 this ();
0180 setFormat(format);
0181 }
0182
0183 /**
0184 * Sets the format that dictates the legal values that can be edited
0185 * and displayed.
0186 *
0187 * @param format <code>Format</code> instance used for converting
0188 * from/to Strings
0189 */
0190 public void setFormat(Format format) {
0191 this .format = format;
0192 }
0193
0194 /**
0195 * Returns the format that dictates the legal values that can be edited
0196 * and displayed.
0197 *
0198 * @return Format instance used for converting from/to Strings
0199 */
0200 public Format getFormat() {
0201 return format;
0202 }
0203
0204 /**
0205 * Sets the minimum permissible value. If the <code>valueClass</code> has
0206 * not been specified, and <code>minimum</code> is non null, the
0207 * <code>valueClass</code> will be set to that of the class of
0208 * <code>minimum</code>.
0209 *
0210 * @param minimum Minimum legal value that can be input
0211 * @see #setValueClass
0212 */
0213 public void setMinimum(Comparable minimum) {
0214 if (getValueClass() == null && minimum != null) {
0215 setValueClass(minimum.getClass());
0216 }
0217 min = minimum;
0218 }
0219
0220 /**
0221 * Returns the minimum permissible value.
0222 *
0223 * @return Minimum legal value that can be input
0224 */
0225 public Comparable getMinimum() {
0226 return min;
0227 }
0228
0229 /**
0230 * Sets the maximum permissible value. If the <code>valueClass</code> has
0231 * not been specified, and <code>max</code> is non null, the
0232 * <code>valueClass</code> will be set to that of the class of
0233 * <code>max</code>.
0234 *
0235 * @param max Maximum legal value that can be input
0236 * @see #setValueClass
0237 */
0238 public void setMaximum(Comparable max) {
0239 if (getValueClass() == null && max != null) {
0240 setValueClass(max.getClass());
0241 }
0242 this .max = max;
0243 }
0244
0245 /**
0246 * Returns the maximum permissible value.
0247 *
0248 * @return Maximum legal value that can be input
0249 */
0250 public Comparable getMaximum() {
0251 return max;
0252 }
0253
0254 /**
0255 * Installs the <code>DefaultFormatter</code> onto a particular
0256 * <code>JFormattedTextField</code>.
0257 * This will invoke <code>valueToString</code> to convert the
0258 * current value from the <code>JFormattedTextField</code> to
0259 * a String. This will then install the <code>Action</code>s from
0260 * <code>getActions</code>, the <code>DocumentFilter</code>
0261 * returned from <code>getDocumentFilter</code> and the
0262 * <code>NavigationFilter</code> returned from
0263 * <code>getNavigationFilter</code> onto the
0264 * <code>JFormattedTextField</code>.
0265 * <p>
0266 * Subclasses will typically only need to override this if they
0267 * wish to install additional listeners on the
0268 * <code>JFormattedTextField</code>.
0269 * <p>
0270 * If there is a <code>ParseException</code> in converting the
0271 * current value to a String, this will set the text to an empty
0272 * String, and mark the <code>JFormattedTextField</code> as being
0273 * in an invalid state.
0274 * <p>
0275 * While this is a public method, this is typically only useful
0276 * for subclassers of <code>JFormattedTextField</code>.
0277 * <code>JFormattedTextField</code> will invoke this method at
0278 * the appropriate times when the value changes, or its internal
0279 * state changes.
0280 *
0281 * @param ftf JFormattedTextField to format for, may be null indicating
0282 * uninstall from current JFormattedTextField.
0283 */
0284 public void install(JFormattedTextField ftf) {
0285 super .install(ftf);
0286 updateMaskIfNecessary();
0287 // invoked again as the mask should now be valid.
0288 positionCursorAtInitialLocation();
0289 }
0290
0291 /**
0292 * Returns a String representation of the Object <code>value</code>.
0293 * This invokes <code>format</code> on the current <code>Format</code>.
0294 *
0295 * @throws ParseException if there is an error in the conversion
0296 * @param value Value to convert
0297 * @return String representation of value
0298 */
0299 public String valueToString(Object value) throws ParseException {
0300 if (value == null) {
0301 return "";
0302 }
0303 Format f = getFormat();
0304
0305 if (f == null) {
0306 return value.toString();
0307 }
0308 return f.format(value);
0309 }
0310
0311 /**
0312 * Returns the <code>Object</code> representation of the
0313 * <code>String</code> <code>text</code>.
0314 *
0315 * @param text <code>String</code> to convert
0316 * @return <code>Object</code> representation of text
0317 * @throws ParseException if there is an error in the conversion
0318 */
0319 public Object stringToValue(String text) throws ParseException {
0320 Object value = stringToValue(text, getFormat());
0321
0322 // Convert to the value class if the Value returned from the
0323 // Format does not match.
0324 if (value != null && getValueClass() != null
0325 && !getValueClass().isInstance(value)) {
0326 value = super .stringToValue(value.toString());
0327 }
0328 try {
0329 if (!isValidValue(value, true)) {
0330 throw new ParseException(
0331 "Value not within min/max range", 0);
0332 }
0333 } catch (ClassCastException cce) {
0334 throw new ParseException(
0335 "Class cast exception comparing values: " + cce, 0);
0336 }
0337 return value;
0338 }
0339
0340 /**
0341 * Returns the <code>Format.Field</code> constants associated with
0342 * the text at <code>offset</code>. If <code>offset</code> is not
0343 * a valid location into the current text, this will return an
0344 * empty array.
0345 *
0346 * @param offset offset into text to be examined
0347 * @return Format.Field constants associated with the text at the
0348 * given position.
0349 */
0350 public Format.Field[] getFields(int offset) {
0351 if (getAllowsInvalid()) {
0352 // This will work if the currently edited value is valid.
0353 updateMask();
0354 }
0355
0356 Map attrs = getAttributes(offset);
0357
0358 if (attrs != null && attrs.size() > 0) {
0359 ArrayList al = new ArrayList();
0360
0361 al.addAll(attrs.keySet());
0362 return (Format.Field[]) al.toArray(EMPTY_FIELD_ARRAY);
0363 }
0364 return EMPTY_FIELD_ARRAY;
0365 }
0366
0367 /**
0368 * Creates a copy of the DefaultFormatter.
0369 *
0370 * @return copy of the DefaultFormatter
0371 */
0372 public Object clone() throws CloneNotSupportedException {
0373 InternationalFormatter formatter = (InternationalFormatter) super
0374 .clone();
0375
0376 formatter.literalMask = null;
0377 formatter.iterator = null;
0378 formatter.validMask = false;
0379 formatter.string = null;
0380 return formatter;
0381 }
0382
0383 /**
0384 * If <code>getSupportsIncrement</code> returns true, this returns
0385 * two Actions suitable for incrementing/decrementing the value.
0386 */
0387 protected Action[] getActions() {
0388 if (getSupportsIncrement()) {
0389 return new Action[] { new IncrementAction("increment", 1),
0390 new IncrementAction("decrement", -1) };
0391 }
0392 return null;
0393 }
0394
0395 /**
0396 * Invokes <code>parseObject</code> on <code>f</code>, returning
0397 * its value.
0398 */
0399 Object stringToValue(String text, Format f) throws ParseException {
0400 if (f == null) {
0401 return text;
0402 }
0403 return f.parseObject(text);
0404 }
0405
0406 /**
0407 * Returns true if <code>value</code> is between the min/max.
0408 *
0409 * @param wantsCCE If false, and a ClassCastException is thrown in
0410 * comparing the values, the exception is consumed and
0411 * false is returned.
0412 */
0413 boolean isValidValue(Object value, boolean wantsCCE) {
0414 Comparable min = getMinimum();
0415
0416 try {
0417 if (min != null && min.compareTo(value) > 0) {
0418 return false;
0419 }
0420 } catch (ClassCastException cce) {
0421 if (wantsCCE) {
0422 throw cce;
0423 }
0424 return false;
0425 }
0426
0427 Comparable max = getMaximum();
0428 try {
0429 if (max != null && max.compareTo(value) < 0) {
0430 return false;
0431 }
0432 } catch (ClassCastException cce) {
0433 if (wantsCCE) {
0434 throw cce;
0435 }
0436 return false;
0437 }
0438 return true;
0439 }
0440
0441 /**
0442 * Returns a Set of the attribute identifiers at <code>index</code>.
0443 */
0444 Map getAttributes(int index) {
0445 if (isValidMask()) {
0446 AttributedCharacterIterator iterator = getIterator();
0447
0448 if (index >= 0 && index <= iterator.getEndIndex()) {
0449 iterator.setIndex(index);
0450 return iterator.getAttributes();
0451 }
0452 }
0453 return null;
0454 }
0455
0456 /**
0457 * Returns the start of the first run that contains the attribute
0458 * <code>id</code>. This will return <code>-1</code> if the attribute
0459 * can not be found.
0460 */
0461 int getAttributeStart(AttributedCharacterIterator.Attribute id) {
0462 if (isValidMask()) {
0463 AttributedCharacterIterator iterator = getIterator();
0464
0465 iterator.first();
0466 while (iterator.current() != CharacterIterator.DONE) {
0467 if (iterator.getAttribute(id) != null) {
0468 return iterator.getIndex();
0469 }
0470 iterator.next();
0471 }
0472 }
0473 return -1;
0474 }
0475
0476 /**
0477 * Returns the <code>AttributedCharacterIterator</code> used to
0478 * format the last value.
0479 */
0480 AttributedCharacterIterator getIterator() {
0481 return iterator;
0482 }
0483
0484 /**
0485 * Updates the AttributedCharacterIterator and bitset, if necessary.
0486 */
0487 void updateMaskIfNecessary() {
0488 if (!getAllowsInvalid() && (getFormat() != null)) {
0489 if (!isValidMask()) {
0490 updateMask();
0491 } else {
0492 String newString = getFormattedTextField().getText();
0493
0494 if (!newString.equals(string)) {
0495 updateMask();
0496 }
0497 }
0498 }
0499 }
0500
0501 /**
0502 * Updates the AttributedCharacterIterator by invoking
0503 * <code>formatToCharacterIterator</code> on the <code>Format</code>.
0504 * If this is successful,
0505 * <code>updateMask(AttributedCharacterIterator)</code>
0506 * is then invoked to update the internal bitmask.
0507 */
0508 void updateMask() {
0509 if (getFormat() != null) {
0510 Document doc = getFormattedTextField().getDocument();
0511
0512 validMask = false;
0513 if (doc != null) {
0514 try {
0515 string = doc.getText(0, doc.getLength());
0516 } catch (BadLocationException ble) {
0517 string = null;
0518 }
0519 if (string != null) {
0520 try {
0521 Object value = stringToValue(string);
0522 AttributedCharacterIterator iterator = getFormat()
0523 .formatToCharacterIterator(value);
0524
0525 updateMask(iterator);
0526 } catch (ParseException pe) {
0527 } catch (IllegalArgumentException iae) {
0528 } catch (NullPointerException npe) {
0529 }
0530 }
0531 }
0532 }
0533 }
0534
0535 /**
0536 * Returns the number of literal characters before <code>index</code>.
0537 */
0538 int getLiteralCountTo(int index) {
0539 int lCount = 0;
0540
0541 for (int counter = 0; counter < index; counter++) {
0542 if (isLiteral(counter)) {
0543 lCount++;
0544 }
0545 }
0546 return lCount;
0547 }
0548
0549 /**
0550 * Returns true if the character at index is a literal, that is
0551 * not editable.
0552 */
0553 boolean isLiteral(int index) {
0554 if (isValidMask() && index < string.length()) {
0555 return literalMask.get(index);
0556 }
0557 return false;
0558 }
0559
0560 /**
0561 * Returns the literal character at index.
0562 */
0563 char getLiteral(int index) {
0564 if (isValidMask() && string != null && index < string.length()) {
0565 return string.charAt(index);
0566 }
0567 return (char) 0;
0568 }
0569
0570 /**
0571 * Returns true if the character at offset is navigatable too. This
0572 * is implemented in terms of <code>isLiteral</code>, subclasses
0573 * may wish to provide different behavior.
0574 */
0575 boolean isNavigatable(int offset) {
0576 return !isLiteral(offset);
0577 }
0578
0579 /**
0580 * Overriden to update the mask after invoking supers implementation.
0581 */
0582 void updateValue(Object value) {
0583 super .updateValue(value);
0584 updateMaskIfNecessary();
0585 }
0586
0587 /**
0588 * Overriden to unconditionally allow the replace if
0589 * ignoreDocumentMutate is true.
0590 */
0591 void replace(DocumentFilter.FilterBypass fb, int offset,
0592 int length, String text, AttributeSet attrs)
0593 throws BadLocationException {
0594 if (ignoreDocumentMutate) {
0595 fb.replace(offset, length, text, attrs);
0596 return;
0597 }
0598 super .replace(fb, offset, length, text, attrs);
0599 }
0600
0601 /**
0602 * Returns the index of the next non-literal character starting at
0603 * index. If index is not a literal, it will be returned.
0604 *
0605 * @param direction Amount to increment looking for non-literal
0606 */
0607 private int getNextNonliteralIndex(int index, int direction) {
0608 int max = getFormattedTextField().getDocument().getLength();
0609
0610 while (index >= 0 && index < max) {
0611 if (isLiteral(index)) {
0612 index += direction;
0613 } else {
0614 return index;
0615 }
0616 }
0617 return (direction == -1) ? 0 : max;
0618 }
0619
0620 /**
0621 * Overriden in an attempt to honor the literals.
0622 * <p>
0623 * If we do
0624 * not allow invalid values and are in overwrite mode, this does the
0625 * following for each character in the replacement range:
0626 * <ol>
0627 * <li>If the character is a literal, add it to the string to replace
0628 * with. If there is text to insert and it doesn't match the
0629 * literal, then insert the literal in the the middle of the insert
0630 * text. This allows you to either paste in literals or not and
0631 * get the same behavior.
0632 * <li>If there is no text to insert, replace it with ' '.
0633 * </ol>
0634 * If not in overwrite mode, and there is text to insert it is
0635 * inserted at the next non literal index going forward. If there
0636 * is only text to remove, it is removed from the next non literal
0637 * index going backward.
0638 */
0639 boolean canReplace(ReplaceHolder rh) {
0640 if (!getAllowsInvalid()) {
0641 String text = rh.text;
0642 int tl = (text != null) ? text.length() : 0;
0643
0644 if (tl == 0
0645 && rh.length == 1
0646 && getFormattedTextField().getSelectionStart() != rh.offset) {
0647 // Backspace, adjust to actually delete next non-literal.
0648 rh.offset = getNextNonliteralIndex(rh.offset, -1);
0649 }
0650 if (getOverwriteMode()) {
0651 StringBuffer replace = null;
0652
0653 for (int counter = 0, textIndex = 0, max = Math.max(tl,
0654 rh.length); counter < max; counter++) {
0655 if (isLiteral(rh.offset + counter)) {
0656 if (replace != null) {
0657 replace.append(getLiteral(rh.offset
0658 + counter));
0659 }
0660 if (textIndex < tl
0661 && text.charAt(textIndex) == getLiteral(rh.offset
0662 + counter)) {
0663 textIndex++;
0664 } else if (textIndex == 0) {
0665 rh.offset++;
0666 rh.length--;
0667 counter--;
0668 max--;
0669 } else if (replace == null) {
0670 replace = new StringBuffer(max);
0671 replace
0672 .append(text
0673 .substring(0, textIndex));
0674 replace.append(getLiteral(rh.offset
0675 + counter));
0676 }
0677 } else if (textIndex < tl) {
0678 if (replace != null) {
0679 replace.append(text.charAt(textIndex));
0680 }
0681 textIndex++;
0682 } else {
0683 // Nothing to replace it with, assume ' '
0684 if (replace == null) {
0685 replace = new StringBuffer(max);
0686 if (textIndex > 0) {
0687 replace.append(text.substring(0,
0688 textIndex));
0689 }
0690 }
0691 if (replace != null) {
0692 replace.append(' ');
0693 }
0694 }
0695 }
0696 if (replace != null) {
0697 rh.text = replace.toString();
0698 }
0699 } else if (tl > 0) {
0700 // insert (or insert and remove)
0701 rh.offset = getNextNonliteralIndex(rh.offset, 1);
0702 } else {
0703 // remove only
0704 rh.offset = getNextNonliteralIndex(rh.offset, -1);
0705 }
0706 ((ExtendedReplaceHolder) rh).endOffset = rh.offset;
0707 ((ExtendedReplaceHolder) rh).endTextLength = (rh.text != null) ? rh.text
0708 .length()
0709 : 0;
0710 } else {
0711 ((ExtendedReplaceHolder) rh).endOffset = rh.offset;
0712 ((ExtendedReplaceHolder) rh).endTextLength = (rh.text != null) ? rh.text
0713 .length()
0714 : 0;
0715 }
0716 boolean can = super .canReplace(rh);
0717 if (can && !getAllowsInvalid()) {
0718 ((ExtendedReplaceHolder) rh).resetFromValue(this );
0719 }
0720 return can;
0721 }
0722
0723 /**
0724 * When in !allowsInvalid mode the text is reset on every edit, thus
0725 * supers implementation will position the cursor at the wrong position.
0726 * As such, this invokes supers implementation and then invokes
0727 * <code>repositionCursor</code> to correctly reset the cursor.
0728 */
0729 boolean replace(ReplaceHolder rh) throws BadLocationException {
0730 int start = -1;
0731 int direction = 1;
0732 int literalCount = -1;
0733
0734 if (rh.length > 0
0735 && (rh.text == null || rh.text.length() == 0)
0736 && (getFormattedTextField().getSelectionStart() != rh.offset || rh.length > 1)) {
0737 direction = -1;
0738 }
0739 if (!getAllowsInvalid()) {
0740 if ((rh.text == null || rh.text.length() == 0)
0741 && rh.length > 0) {
0742 // remove
0743 start = getFormattedTextField().getSelectionStart();
0744 } else {
0745 start = rh.offset;
0746 }
0747 literalCount = getLiteralCountTo(start);
0748 }
0749 if (super .replace(rh)) {
0750 if (start != -1) {
0751 int end = ((ExtendedReplaceHolder) rh).endOffset;
0752
0753 end += ((ExtendedReplaceHolder) rh).endTextLength;
0754 repositionCursor(literalCount, end, direction);
0755 } else {
0756 start = ((ExtendedReplaceHolder) rh).endOffset;
0757 if (direction == 1) {
0758 start += ((ExtendedReplaceHolder) rh).endTextLength;
0759 }
0760 repositionCursor(start, direction);
0761 }
0762 return true;
0763 }
0764 return false;
0765 }
0766
0767 /**
0768 * Repositions the cursor. <code>startLiteralCount</code> gives
0769 * the number of literals to the start of the deleted range, end
0770 * gives the ending location to adjust from, direction gives
0771 * the direction relative to <code>end</code> to position the
0772 * cursor from.
0773 */
0774 private void repositionCursor(int startLiteralCount, int end,
0775 int direction) {
0776 int endLiteralCount = getLiteralCountTo(end);
0777
0778 if (endLiteralCount != end) {
0779 end -= startLiteralCount;
0780 for (int counter = 0; counter < end; counter++) {
0781 if (isLiteral(counter)) {
0782 end++;
0783 }
0784 }
0785 }
0786 repositionCursor(end, 1 /*direction*/);
0787 }
0788
0789 /**
0790 * Returns the character from the mask that has been buffered
0791 * at <code>index</code>.
0792 */
0793 char getBufferedChar(int index) {
0794 if (isValidMask()) {
0795 if (string != null && index < string.length()) {
0796 return string.charAt(index);
0797 }
0798 }
0799 return (char) 0;
0800 }
0801
0802 /**
0803 * Returns true if the current mask is valid.
0804 */
0805 boolean isValidMask() {
0806 return validMask;
0807 }
0808
0809 /**
0810 * Returns true if <code>attributes</code> is null or empty.
0811 */
0812 boolean isLiteral(Map attributes) {
0813 return ((attributes == null) || attributes.size() == 0);
0814 }
0815
0816 /**
0817 * Updates the interal bitset from <code>iterator</code>. This will
0818 * set <code>validMask</code> to true if <code>iterator</code> is
0819 * non-null.
0820 */
0821 private void updateMask(AttributedCharacterIterator iterator) {
0822 if (iterator != null) {
0823 validMask = true;
0824 this .iterator = iterator;
0825
0826 // Update the literal mask
0827 if (literalMask == null) {
0828 literalMask = new BitSet();
0829 } else {
0830 for (int counter = literalMask.length() - 1; counter >= 0; counter--) {
0831 literalMask.clear(counter);
0832 }
0833 }
0834
0835 iterator.first();
0836 while (iterator.current() != CharacterIterator.DONE) {
0837 Map attributes = iterator.getAttributes();
0838 boolean set = isLiteral(attributes);
0839 int start = iterator.getIndex();
0840 int end = iterator.getRunLimit();
0841
0842 while (start < end) {
0843 if (set) {
0844 literalMask.set(start);
0845 } else {
0846 literalMask.clear(start);
0847 }
0848 start++;
0849 }
0850 iterator.setIndex(start);
0851 }
0852 }
0853 }
0854
0855 /**
0856 * Returns true if <code>field</code> is non-null.
0857 * Subclasses that wish to allow incrementing to happen outside of
0858 * the known fields will need to override this.
0859 */
0860 boolean canIncrement(Object field, int cursorPosition) {
0861 return (field != null);
0862 }
0863
0864 /**
0865 * Selects the fields identified by <code>attributes</code>.
0866 */
0867 void selectField(Object f, int count) {
0868 AttributedCharacterIterator iterator = getIterator();
0869
0870 if (iterator != null
0871 && (f instanceof AttributedCharacterIterator.Attribute)) {
0872 AttributedCharacterIterator.Attribute field = (AttributedCharacterIterator.Attribute) f;
0873
0874 iterator.first();
0875 while (iterator.current() != CharacterIterator.DONE) {
0876 while (iterator.getAttribute(field) == null
0877 && iterator.next() != CharacterIterator.DONE)
0878 ;
0879 if (iterator.current() != CharacterIterator.DONE) {
0880 int limit = iterator.getRunLimit(field);
0881
0882 if (--count <= 0) {
0883 getFormattedTextField().select(
0884 iterator.getIndex(), limit);
0885 break;
0886 }
0887 iterator.setIndex(limit);
0888 iterator.next();
0889 }
0890 }
0891 }
0892 }
0893
0894 /**
0895 * Returns the field that will be adjusted by adjustValue.
0896 */
0897 Object getAdjustField(int start, Map attributes) {
0898 return null;
0899 }
0900
0901 /**
0902 * Returns the number of occurences of <code>f</code> before
0903 * the location <code>start</code> in the current
0904 * <code>AttributedCharacterIterator</code>.
0905 */
0906 private int getFieldTypeCountTo(Object f, int start) {
0907 AttributedCharacterIterator iterator = getIterator();
0908 int count = 0;
0909
0910 if (iterator != null
0911 && (f instanceof AttributedCharacterIterator.Attribute)) {
0912 AttributedCharacterIterator.Attribute field = (AttributedCharacterIterator.Attribute) f;
0913 int index = 0;
0914
0915 iterator.first();
0916 while (iterator.getIndex() < start) {
0917 while (iterator.getAttribute(field) == null
0918 && iterator.next() != CharacterIterator.DONE)
0919 ;
0920 if (iterator.current() != CharacterIterator.DONE) {
0921 iterator.setIndex(iterator.getRunLimit(field));
0922 iterator.next();
0923 count++;
0924 } else {
0925 break;
0926 }
0927 }
0928 }
0929 return count;
0930 }
0931
0932 /**
0933 * Subclasses supporting incrementing must override this to handle
0934 * the actual incrementing. <code>value</code> is the current value,
0935 * <code>attributes</code> gives the field the cursor is in (may be
0936 * null depending upon <code>canIncrement</code>) and
0937 * <code>direction</code> is the amount to increment by.
0938 */
0939 Object adjustValue(Object value, Map attributes, Object field,
0940 int direction) throws BadLocationException, ParseException {
0941 return null;
0942 }
0943
0944 /**
0945 * Returns false, indicating InternationalFormatter does not allow
0946 * incrementing of the value. Subclasses that wish to support
0947 * incrementing/decrementing the value should override this and
0948 * return true. Subclasses should also override
0949 * <code>adjustValue</code>.
0950 */
0951 boolean getSupportsIncrement() {
0952 return false;
0953 }
0954
0955 /**
0956 * Resets the value of the JFormattedTextField to be
0957 * <code>value</code>.
0958 */
0959 void resetValue(Object value) throws BadLocationException,
0960 ParseException {
0961 Document doc = getFormattedTextField().getDocument();
0962 String string = valueToString(value);
0963
0964 try {
0965 ignoreDocumentMutate = true;
0966 doc.remove(0, doc.getLength());
0967 doc.insertString(0, string, null);
0968 } finally {
0969 ignoreDocumentMutate = false;
0970 }
0971 updateValue(value);
0972 }
0973
0974 /**
0975 * Subclassed to update the internal representation of the mask after
0976 * the default read operation has completed.
0977 */
0978 private void readObject(ObjectInputStream s) throws IOException,
0979 ClassNotFoundException {
0980 s.defaultReadObject();
0981 updateMaskIfNecessary();
0982 }
0983
0984 /**
0985 * Overriden to return an instance of <code>ExtendedReplaceHolder</code>.
0986 */
0987 ReplaceHolder getReplaceHolder(DocumentFilter.FilterBypass fb,
0988 int offset, int length, String text, AttributeSet attrs) {
0989 if (replaceHolder == null) {
0990 replaceHolder = new ExtendedReplaceHolder();
0991 }
0992 return super .getReplaceHolder(fb, offset, length, text, attrs);
0993 }
0994
0995 /**
0996 * As InternationalFormatter replaces the complete text on every edit,
0997 * ExtendedReplaceHolder keeps track of the offset and length passed
0998 * into canReplace.
0999 */
1000 static class ExtendedReplaceHolder extends ReplaceHolder {
1001 /** Offset of the insert/remove. This may differ from offset in
1002 * that if !allowsInvalid the text is replaced on every edit. */
1003 int endOffset;
1004 /** Length of the text. This may differ from text.length in
1005 * that if !allowsInvalid the text is replaced on every edit. */
1006 int endTextLength;
1007
1008 /**
1009 * Resets the region to delete to be the complete document and
1010 * the text from invoking valueToString on the current value.
1011 */
1012 void resetFromValue(InternationalFormatter formatter) {
1013 // Need to reset the complete string as Format's result can
1014 // be completely different.
1015 offset = 0;
1016 try {
1017 text = formatter.valueToString(value);
1018 } catch (ParseException pe) {
1019 // Should never happen, otherwise canReplace would have
1020 // returned value.
1021 text = "";
1022 }
1023 length = fb.getDocument().getLength();
1024 }
1025 }
1026
1027 /**
1028 * IncrementAction is used to increment the value by a certain amount.
1029 * It calls into <code>adjustValue</code> to handle the actual
1030 * incrementing of the value.
1031 */
1032 private class IncrementAction extends AbstractAction {
1033 private int direction;
1034
1035 IncrementAction(String name, int direction) {
1036 super (name);
1037 this .direction = direction;
1038 }
1039
1040 public void actionPerformed(ActionEvent ae) {
1041
1042 if (getFormattedTextField().isEditable()) {
1043 if (getAllowsInvalid()) {
1044 // This will work if the currently edited value is valid.
1045 updateMask();
1046 }
1047
1048 boolean validEdit = false;
1049
1050 if (isValidMask()) {
1051 int start = getFormattedTextField()
1052 .getSelectionStart();
1053
1054 if (start != -1) {
1055 AttributedCharacterIterator iterator = getIterator();
1056
1057 iterator.setIndex(start);
1058
1059 Map attributes = iterator.getAttributes();
1060 Object field = getAdjustField(start, attributes);
1061
1062 if (canIncrement(field, start)) {
1063 try {
1064 Object value = stringToValue(getFormattedTextField()
1065 .getText());
1066 int fieldTypeCount = getFieldTypeCountTo(
1067 field, start);
1068
1069 value = adjustValue(value, attributes,
1070 field, direction);
1071 if (value != null
1072 && isValidValue(value, false)) {
1073 resetValue(value);
1074 updateMask();
1075
1076 if (isValidMask()) {
1077 selectField(field,
1078 fieldTypeCount);
1079 }
1080 validEdit = true;
1081 }
1082 } catch (ParseException pe) {
1083 } catch (BadLocationException ble) {
1084 }
1085 }
1086 }
1087 }
1088 if (!validEdit) {
1089 invalidEdit();
1090 }
1091 }
1092 }
1093 }
1094 }
|