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™ 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 navigable 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 occurrences 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 }