< prev index next >

src/java.desktop/share/classes/javax/swing/text/DefaultFormatter.java

Print this page




  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 sun.reflect.misc.ReflectUtil;
  28 import sun.swing.SwingUtilities2;
  29 
  30 import java.io.Serializable;
  31 import java.lang.reflect.*;
  32 import java.text.ParseException;
  33 import javax.swing.*;
  34 import javax.swing.text.*;
  35 
  36 /**
  37  * <code>DefaultFormatter</code> formats arbitrary objects. Formatting is done
  38  * by invoking the <code>toString</code> method. In order to convert the
  39  * value back to a String, your class must provide a constructor that
  40  * takes a String argument. If no single argument constructor that takes a
  41  * String is found, the returned value will be the String passed into
  42  * <code>stringToValue</code>.
  43  * <p>
  44  * Instances of <code>DefaultFormatter</code> can not be used in multiple
  45  * instances of <code>JFormattedTextField</code>. To obtain a copy of
  46  * an already configured <code>DefaultFormatter</code>, use the
  47  * <code>clone</code> method.
  48  * <p>
  49  * <strong>Warning:</strong>
  50  * Serialized objects of this class will not be compatible with
  51  * future Swing releases. The current serialization support is
  52  * appropriate for short term storage or RMI between applications running
  53  * the same version of Swing.  As of 1.4, support for long term storage
  54  * of all JavaBeans&trade;
  55  * has been added to the <code>java.beans</code> package.
  56  * Please see {@link java.beans.XMLEncoder}.
  57  *
  58  * @see javax.swing.JFormattedTextField.AbstractFormatter
  59  *
  60  * @since 1.4
  61  */
  62 @SuppressWarnings("serial") // Same-version serialization only
  63 public class DefaultFormatter extends JFormattedTextField.AbstractFormatter
  64                     implements Cloneable, Serializable {
  65     /** Indicates if the value being edited must match the mask. */
  66     private boolean allowsInvalid;
  67 
  68     /** If true, editing mode is in overwrite (or strikethough). */
  69     private boolean overwriteMode;
  70 
  71     /** If true, any time a valid edit happens commitEdit is invoked. */
  72     private boolean commitOnEdit;
  73 
  74     /** Class used to create new instances. */
  75     private Class<?> valueClass;
  76 
  77     /** NavigationFilter that forwards calls back to DefaultFormatter. */
  78     private NavigationFilter navigationFilter;
  79 
  80     /** DocumentFilter that forwards calls back to DefaultFormatter. */
  81     private DocumentFilter documentFilter;
  82 
  83     /** Used during replace to track the region to replace. */
  84     transient ReplaceHolder replaceHolder;
  85 
  86 
  87     /**
  88      * Creates a DefaultFormatter.
  89      */
  90     public DefaultFormatter() {
  91         overwriteMode = true;
  92         allowsInvalid = true;
  93     }
  94 
  95     /**
  96      * Installs the <code>DefaultFormatter</code> onto a particular
  97      * <code>JFormattedTextField</code>.
  98      * This will invoke <code>valueToString</code> to convert the
  99      * current value from the <code>JFormattedTextField</code> to
 100      * a String. This will then install the <code>Action</code>s from
 101      * <code>getActions</code>, the <code>DocumentFilter</code>
 102      * returned from <code>getDocumentFilter</code> and the
 103      * <code>NavigationFilter</code> returned from
 104      * <code>getNavigationFilter</code> onto the
 105      * <code>JFormattedTextField</code>.
 106      * <p>
 107      * Subclasses will typically only need to override this if they
 108      * wish to install additional listeners on the
 109      * <code>JFormattedTextField</code>.
 110      * <p>
 111      * If there is a <code>ParseException</code> in converting the
 112      * current value to a String, this will set the text to an empty
 113      * String, and mark the <code>JFormattedTextField</code> as being
 114      * in an invalid state.
 115      * <p>
 116      * While this is a public method, this is typically only useful
 117      * for subclassers of <code>JFormattedTextField</code>.
 118      * <code>JFormattedTextField</code> will invoke this method at
 119      * the appropriate times when the value changes, or its internal
 120      * state changes.
 121      *
 122      * @param ftf JFormattedTextField to format for, may be null indicating
 123      *            uninstall from current JFormattedTextField.
 124      */
 125     public void install(JFormattedTextField ftf) {
 126         super.install(ftf);
 127         positionCursorAtInitialLocation();
 128     }
 129 
 130     /**
 131      * Sets when edits are published back to the
 132      * <code>JFormattedTextField</code>. If true, <code>commitEdit</code>
 133      * is invoked after every valid edit (any time the text is edited). On
 134      * the other hand, if this is false than the <code>DefaultFormatter</code>
 135      * does not publish edits back to the <code>JFormattedTextField</code>.
 136      * As such, the only time the value of the <code>JFormattedTextField</code>
 137      * will change is when <code>commitEdit</code> is invoked on
 138      * <code>JFormattedTextField</code>, typically when enter is pressed
 139      * or focus leaves the <code>JFormattedTextField</code>.
 140      *
 141      * @param commit Used to indicate when edits are committed back to the
 142      *               JTextComponent
 143      */
 144     public void setCommitsOnValidEdit(boolean commit) {
 145         commitOnEdit = commit;
 146     }
 147 
 148     /**
 149      * Returns when edits are published back to the
 150      * <code>JFormattedTextField</code>.
 151      *
 152      * @return true if edits are committed after every valid edit
 153      */
 154     public boolean getCommitsOnValidEdit() {
 155         return commitOnEdit;
 156     }
 157 
 158     /**
 159      * Configures the behavior when inserting characters. If
 160      * <code>overwriteMode</code> is true (the default), new characters
 161      * overwrite existing characters in the model.
 162      *
 163      * @param overwriteMode Indicates if overwrite or overstrike mode is used
 164      */
 165     public void setOverwriteMode(boolean overwriteMode) {
 166         this.overwriteMode = overwriteMode;
 167     }
 168 
 169     /**
 170      * Returns the behavior when inserting characters.
 171      *
 172      * @return true if newly inserted characters overwrite existing characters
 173      */
 174     public boolean getOverwriteMode() {
 175         return overwriteMode;
 176     }
 177 
 178     /**
 179      * Sets whether or not the value being edited is allowed to be invalid
 180      * for a length of time (that is, <code>stringToValue</code> throws
 181      * a <code>ParseException</code>).
 182      * It is often convenient to allow the user to temporarily input an
 183      * invalid value.
 184      *
 185      * @param allowsInvalid Used to indicate if the edited value must always
 186      *        be valid
 187      */
 188     public void setAllowsInvalid(boolean allowsInvalid) {
 189         this.allowsInvalid = allowsInvalid;
 190     }
 191 
 192     /**
 193      * Returns whether or not the value being edited is allowed to be invalid
 194      * for a length of time.
 195      *
 196      * @return false if the edited value must always be valid
 197      */
 198     public boolean getAllowsInvalid() {
 199         return allowsInvalid;
 200     }
 201 


 205      * takes a String, String values will be used.
 206      *
 207      * @param valueClass Class used to construct return value from
 208      *        stringToValue
 209      */
 210     public void setValueClass(Class<?> valueClass) {
 211         this.valueClass = valueClass;
 212     }
 213 
 214     /**
 215      * Returns that class that is used to create new Objects.
 216      *
 217      * @return Class used to construct return value from stringToValue
 218      */
 219     public Class<?> getValueClass() {
 220         return valueClass;
 221     }
 222 
 223     /**
 224      * Converts the passed in String into an instance of
 225      * <code>getValueClass</code> by way of the constructor that
 226      * takes a String argument. If <code>getValueClass</code>
 227      * returns null, the Class of the current value in the
 228      * <code>JFormattedTextField</code> will be used. If this is null, a
 229      * String will be returned. If the constructor throws an exception, a
 230      * <code>ParseException</code> will be thrown. If there is no single
 231      * argument String constructor, <code>string</code> will be returned.
 232      *
 233      * @throws ParseException if there is an error in the conversion
 234      * @param string String to convert
 235      * @return Object representation of text
 236      */
 237     public Object stringToValue(String string) throws ParseException {
 238         Class<?> vc = getValueClass();
 239         JFormattedTextField ftf = getFormattedTextField();
 240 
 241         if (vc == null && ftf != null) {
 242             Object value = ftf.getValue();
 243 
 244             if (value != null) {
 245                 vc = value.getClass();
 246             }
 247         }
 248         if (vc != null) {
 249             Constructor<?> cons;
 250 
 251             try {


 254                 cons = vc.getConstructor(new Class<?>[]{String.class});
 255 
 256             } catch (NoSuchMethodException nsme) {
 257                 cons = null;
 258             }
 259 
 260             if (cons != null) {
 261                 try {
 262                     SwingUtilities2.checkAccess(cons.getModifiers());
 263                     return cons.newInstance(new Object[] { string });
 264                 } catch (Throwable ex) {
 265                     throw new ParseException("Error creating instance", 0);
 266                 }
 267             }
 268         }
 269         return string;
 270     }
 271 
 272     /**
 273      * Converts the passed in Object into a String by way of the
 274      * <code>toString</code> method.
 275      *
 276      * @throws ParseException if there is an error in the conversion
 277      * @param value Value to convert
 278      * @return String representation of value
 279      */
 280     public String valueToString(Object value) throws ParseException {
 281         if (value == null) {
 282             return "";
 283         }
 284         return value.toString();
 285     }
 286 
 287     /**
 288      * Returns the <code>DocumentFilter</code> used to restrict the characters
 289      * that can be input into the <code>JFormattedTextField</code>.
 290      *
 291      * @return DocumentFilter to restrict edits
 292      */
 293     protected DocumentFilter getDocumentFilter() {
 294         if (documentFilter == null) {
 295             documentFilter = new DefaultDocumentFilter();
 296         }
 297         return documentFilter;
 298     }
 299 
 300     /**
 301      * Returns the <code>NavigationFilter</code> used to restrict where the
 302      * cursor can be placed.
 303      *
 304      * @return NavigationFilter to restrict navigation
 305      */
 306     protected NavigationFilter getNavigationFilter() {
 307         if (navigationFilter == null) {
 308             navigationFilter = new DefaultNavigationFilter();
 309         }
 310         return navigationFilter;
 311     }
 312 
 313     /**
 314      * Creates a copy of the DefaultFormatter.
 315      *
 316      * @return copy of the DefaultFormatter
 317      */
 318     public Object clone() throws CloneNotSupportedException {
 319         DefaultFormatter formatter = (DefaultFormatter)super.clone();
 320 
 321         formatter.navigationFilter = null;
 322         formatter.documentFilter = null;
 323         formatter.replaceHolder = null;
 324         return formatter;
 325     }
 326 
 327 
 328     /**
 329      * Positions the cursor at the initial location.
 330      */
 331     void positionCursorAtInitialLocation() {
 332         JFormattedTextField ftf = getFormattedTextField();
 333         if (ftf != null) {
 334             ftf.setCaretPosition(getInitialVisualPosition());
 335         }
 336     }
 337 
 338     /**
 339      * Returns the initial location to position the cursor at. This forwards
 340      * the call to <code>getNextNavigatableChar</code>.
 341      */
 342     int getInitialVisualPosition() {
 343         return getNextNavigatableChar(0, 1);
 344     }
 345 
 346     /**
 347      * Subclasses should override this if they want cursor navigation
 348      * to skip certain characters. A return value of false indicates
 349      * the character at <code>offset</code> should be skipped when
 350      * navigating throught the field.
 351      */
 352     boolean isNavigatable(int offset) {
 353         return true;
 354     }
 355 
 356     /**
 357      * Returns true if the text in <code>text</code> can be inserted.  This
 358      * does not mean the text will ultimately be inserted, it is used if
 359      * text can trivially reject certain characters.
 360      */
 361     boolean isLegalInsertText(String text) {
 362         return true;
 363     }
 364 
 365     /**
 366      * Returns the next editable character starting at offset incrementing
 367      * the offset by <code>direction</code>.
 368      */
 369     private int getNextNavigatableChar(int offset, int direction) {
 370         int max = getFormattedTextField().getDocument().getLength();
 371 
 372         while (offset >= 0 && offset < max) {
 373             if (isNavigatable(offset)) {
 374                 return offset;
 375             }
 376             offset += direction;
 377         }
 378         return offset;
 379     }
 380 
 381     /**
 382      * A convenience methods to return the result of deleting
 383      * <code>deleteLength</code> characters at <code>offset</code>
 384      * and inserting <code>replaceString</code> at <code>offset</code>
 385      * in the current text field.
 386      */
 387     String getReplaceString(int offset, int deleteLength,
 388                             String replaceString) {
 389         String string = getFormattedTextField().getText();
 390         String result;
 391 
 392         result = string.substring(0, offset);
 393         if (replaceString != null) {
 394             result += replaceString;
 395         }
 396         if (offset + deleteLength < string.length()) {
 397             result += string.substring(offset + deleteLength);
 398         }
 399         return result;
 400     }
 401 
 402     /*
 403      * Returns true if the operation described by <code>rh</code> will
 404      * result in a legal edit.  This may set the <code>value</code>
 405      * field of <code>rh</code>.
 406      */
 407     boolean isValidEdit(ReplaceHolder rh) {
 408         if (!getAllowsInvalid()) {
 409             String newString = getReplaceString(rh.offset, rh.length, rh.text);
 410 
 411             try {
 412                 rh.value = stringToValue(newString);
 413 
 414                 return true;
 415             } catch (ParseException pe) {
 416                 return false;
 417             }
 418         }
 419         return true;
 420     }
 421 
 422     /**
 423      * Invokes <code>commitEdit</code> on the JFormattedTextField.
 424      */
 425     void commitEdit() throws ParseException {
 426         JFormattedTextField ftf = getFormattedTextField();
 427 
 428         if (ftf != null) {
 429             ftf.commitEdit();
 430         }
 431     }
 432 
 433     /**
 434      * Pushes the value to the JFormattedTextField if the current value
 435      * is valid and invokes <code>setEditValid</code> based on the
 436      * validity of the value.
 437      */
 438     void updateValue() {
 439         updateValue(null);
 440     }
 441 
 442     /**
 443      * Pushes the <code>value</code> to the editor if we are to
 444      * commit on edits. If <code>value</code> is null, the current value
 445      * will be obtained from the text component.
 446      */
 447     void updateValue(Object value) {
 448         try {
 449             if (value == null) {
 450                 String string = getFormattedTextField().getText();
 451 
 452                 value = stringToValue(string);
 453             }
 454 
 455             if (getCommitsOnValidEdit()) {
 456                 commitEdit();
 457             }
 458             setEditValid(true);
 459         } catch (ParseException pe) {
 460             setEditValid(false);
 461         }
 462     }
 463 
 464     /**
 465      * Returns the next cursor position from offset by incrementing
 466      * <code>direction</code>. This uses
 467      * <code>getNextNavigatableChar</code>
 468      * as well as constraining the location to the max position.
 469      */
 470     int getNextCursorPosition(int offset, int direction) {
 471         int newOffset = getNextNavigatableChar(offset, direction);
 472         int max = getFormattedTextField().getDocument().getLength();
 473 
 474         if (!getAllowsInvalid()) {
 475             if (direction == -1 && offset == newOffset) {
 476                 // Case where hit backspace and only characters before
 477                 // offset are fixed.
 478                 newOffset = getNextNavigatableChar(newOffset, 1);
 479                 if (newOffset >= max) {
 480                     newOffset = offset;
 481                 }
 482             }
 483             else if (direction == 1 && newOffset >= max) {
 484                 // Don't go beyond last editable character.
 485                 newOffset = getNextNavigatableChar(max - 1, -1);
 486                 if (newOffset < max) {
 487                     newOffset++;


 522                 value = text.getUI().getNextVisualPositionFrom(
 523                               text, value, bias, direction,biasRet);
 524             }
 525             int max = getFormattedTextField().getDocument().getLength();
 526             if (last == value || value == max) {
 527                 if (value == 0) {
 528                     biasRet[0] = Position.Bias.Forward;
 529                     value = getInitialVisualPosition();
 530                 }
 531                 if (value >= max && max > 0) {
 532                     // Pending: should not assume forward!
 533                     biasRet[0] = Position.Bias.Forward;
 534                     value = getNextNavigatableChar(max - 1, -1) + 1;
 535                 }
 536             }
 537         }
 538         return value;
 539     }
 540 
 541     /**
 542      * Returns true if the edit described by <code>rh</code> will result
 543      * in a legal value.
 544      */
 545     boolean canReplace(ReplaceHolder rh) {
 546         return isValidEdit(rh);
 547     }
 548 
 549     /**
 550      * DocumentFilter method, funnels into <code>replace</code>.
 551      */
 552     void replace(DocumentFilter.FilterBypass fb, int offset,
 553                      int length, String text,
 554                      AttributeSet attrs) throws BadLocationException {
 555         ReplaceHolder rh = getReplaceHolder(fb, offset, length, text, attrs);
 556 
 557         replace(rh);
 558     }
 559 
 560     /**
 561      * If the edit described by <code>rh</code> is legal, this will
 562      * return true, commit the edit (if necessary) and update the cursor
 563      * position.  This forwards to <code>canReplace</code> and
 564      * <code>isLegalInsertText</code> as necessary to determine if
 565      * the edit is in fact legal.
 566      * <p>
 567      * All of the DocumentFilter methods funnel into here, you should
 568      * generally only have to override this.
 569      */
 570     boolean replace(ReplaceHolder rh) throws BadLocationException {
 571         boolean valid = true;
 572         int direction = 1;
 573 
 574         if (rh.length > 0 && (rh.text == null || rh.text.length() == 0) &&
 575                (getFormattedTextField().getSelectionStart() != rh.offset ||
 576                    rh.length > 1)) {
 577             direction = -1;
 578         }
 579 
 580         if (getOverwriteMode() && rh.text != null &&
 581             getFormattedTextField().getSelectedText() == null)
 582         {
 583             rh.length = Math.min(Math.max(rh.length, rh.text.length()),
 584                                  rh.fb.getDocument().getLength() - rh.offset);




  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 sun.reflect.misc.ReflectUtil;
  28 import sun.swing.SwingUtilities2;
  29 
  30 import java.io.Serializable;
  31 import java.lang.reflect.*;
  32 import java.text.ParseException;
  33 import javax.swing.*;
  34 import javax.swing.text.*;
  35 
  36 /**
  37  * {@code DefaultFormatter} formats arbitrary objects. Formatting is done
  38  * by invoking the {@code toString} method. In order to convert the
  39  * value back to a String, your class must provide a constructor that
  40  * takes a String argument. If no single argument constructor that takes a
  41  * String is found, the returned value will be the String passed into
  42  * {@code stringToValue}.
  43  * <p>
  44  * Instances of {@code DefaultFormatter} can not be used in multiple
  45  * instances of {@code JFormattedTextField}. To obtain a copy of
  46  * an already configured {@code DefaultFormatter}, use the
  47  * {@code clone} method.
  48  * <p>
  49  * <strong>Warning:</strong>
  50  * Serialized objects of this class will not be compatible with
  51  * future Swing releases. The current serialization support is
  52  * appropriate for short term storage or RMI between applications running
  53  * the same version of Swing.  As of 1.4, support for long term storage
  54  * of all JavaBeans&trade;
  55  * has been added to the {@code java.beans} package.
  56  * Please see {@link java.beans.XMLEncoder}.
  57  *
  58  * @see javax.swing.JFormattedTextField.AbstractFormatter
  59  *
  60  * @since 1.4
  61  */
  62 @SuppressWarnings("serial") // Same-version serialization only
  63 public class DefaultFormatter extends JFormattedTextField.AbstractFormatter
  64                     implements Cloneable, Serializable {
  65     /** Indicates if the value being edited must match the mask. */
  66     private boolean allowsInvalid;
  67 
  68     /** If true, editing mode is in overwrite (or strikethough). */
  69     private boolean overwriteMode;
  70 
  71     /** If true, any time a valid edit happens commitEdit is invoked. */
  72     private boolean commitOnEdit;
  73 
  74     /** Class used to create new instances. */
  75     private Class<?> valueClass;
  76 
  77     /** NavigationFilter that forwards calls back to DefaultFormatter. */
  78     private NavigationFilter navigationFilter;
  79 
  80     /** DocumentFilter that forwards calls back to DefaultFormatter. */
  81     private DocumentFilter documentFilter;
  82 
  83     /** Used during replace to track the region to replace. */
  84     transient ReplaceHolder replaceHolder;
  85 
  86 
  87     /**
  88      * Creates a DefaultFormatter.
  89      */
  90     public DefaultFormatter() {
  91         overwriteMode = true;
  92         allowsInvalid = true;
  93     }
  94 
  95     /**
  96      * Installs the {@code DefaultFormatter} onto a particular
  97      * {@code JFormattedTextField}.
  98      * This will invoke {@code valueToString} to convert the
  99      * current value from the {@code JFormattedTextField} to
 100      * a String. This will then install the {@code Action}s from
 101      * {@code getActions}, the {@code DocumentFilter}
 102      * returned from {@code getDocumentFilter} and the
 103      * {@code NavigationFilter} returned from
 104      * {@code getNavigationFilter} onto the
 105      * {@code JFormattedTextField}.
 106      * <p>
 107      * Subclasses will typically only need to override this if they
 108      * wish to install additional listeners on the
 109      * {@code JFormattedTextField}.
 110      * <p>
 111      * If there is a {@code ParseException} in converting the
 112      * current value to a String, this will set the text to an empty
 113      * String, and mark the {@code JFormattedTextField} as being
 114      * in an invalid state.
 115      * <p>
 116      * While this is a public method, this is typically only useful
 117      * for subclassers of {@code JFormattedTextField}.
 118      * {@code JFormattedTextField} will invoke this method at
 119      * the appropriate times when the value changes, or its internal
 120      * state changes.
 121      *
 122      * @param ftf JFormattedTextField to format for, may be null indicating
 123      *            uninstall from current JFormattedTextField.
 124      */
 125     public void install(JFormattedTextField ftf) {
 126         super.install(ftf);
 127         positionCursorAtInitialLocation();
 128     }
 129 
 130     /**
 131      * Sets when edits are published back to the
 132      * {@code JFormattedTextField}. If true, {@code commitEdit}
 133      * is invoked after every valid edit (any time the text is edited). On
 134      * the other hand, if this is false than the {@code DefaultFormatter}
 135      * does not publish edits back to the {@code JFormattedTextField}.
 136      * As such, the only time the value of the {@code JFormattedTextField}
 137      * will change is when {@code commitEdit} is invoked on
 138      * {@code JFormattedTextField}, typically when enter is pressed
 139      * or focus leaves the {@code JFormattedTextField}.
 140      *
 141      * @param commit Used to indicate when edits are committed back to the
 142      *               JTextComponent
 143      */
 144     public void setCommitsOnValidEdit(boolean commit) {
 145         commitOnEdit = commit;
 146     }
 147 
 148     /**
 149      * Returns when edits are published back to the
 150      * {@code JFormattedTextField}.
 151      *
 152      * @return true if edits are committed after every valid edit
 153      */
 154     public boolean getCommitsOnValidEdit() {
 155         return commitOnEdit;
 156     }
 157 
 158     /**
 159      * Configures the behavior when inserting characters. If
 160      * {@code overwriteMode} is true (the default), new characters
 161      * overwrite existing characters in the model.
 162      *
 163      * @param overwriteMode Indicates if overwrite or overstrike mode is used
 164      */
 165     public void setOverwriteMode(boolean overwriteMode) {
 166         this.overwriteMode = overwriteMode;
 167     }
 168 
 169     /**
 170      * Returns the behavior when inserting characters.
 171      *
 172      * @return true if newly inserted characters overwrite existing characters
 173      */
 174     public boolean getOverwriteMode() {
 175         return overwriteMode;
 176     }
 177 
 178     /**
 179      * Sets whether or not the value being edited is allowed to be invalid
 180      * for a length of time (that is, {@code stringToValue} throws
 181      * a {@code ParseException}).
 182      * It is often convenient to allow the user to temporarily input an
 183      * invalid value.
 184      *
 185      * @param allowsInvalid Used to indicate if the edited value must always
 186      *        be valid
 187      */
 188     public void setAllowsInvalid(boolean allowsInvalid) {
 189         this.allowsInvalid = allowsInvalid;
 190     }
 191 
 192     /**
 193      * Returns whether or not the value being edited is allowed to be invalid
 194      * for a length of time.
 195      *
 196      * @return false if the edited value must always be valid
 197      */
 198     public boolean getAllowsInvalid() {
 199         return allowsInvalid;
 200     }
 201 


 205      * takes a String, String values will be used.
 206      *
 207      * @param valueClass Class used to construct return value from
 208      *        stringToValue
 209      */
 210     public void setValueClass(Class<?> valueClass) {
 211         this.valueClass = valueClass;
 212     }
 213 
 214     /**
 215      * Returns that class that is used to create new Objects.
 216      *
 217      * @return Class used to construct return value from stringToValue
 218      */
 219     public Class<?> getValueClass() {
 220         return valueClass;
 221     }
 222 
 223     /**
 224      * Converts the passed in String into an instance of
 225      * {@code getValueClass} by way of the constructor that
 226      * takes a String argument. If {@code getValueClass}
 227      * returns null, the Class of the current value in the
 228      * {@code JFormattedTextField} will be used. If this is null, a
 229      * String will be returned. If the constructor throws an exception, a
 230      * {@code ParseException} will be thrown. If there is no single
 231      * argument String constructor, {@code string} will be returned.
 232      *
 233      * @throws ParseException if there is an error in the conversion
 234      * @param string String to convert
 235      * @return Object representation of text
 236      */
 237     public Object stringToValue(String string) throws ParseException {
 238         Class<?> vc = getValueClass();
 239         JFormattedTextField ftf = getFormattedTextField();
 240 
 241         if (vc == null && ftf != null) {
 242             Object value = ftf.getValue();
 243 
 244             if (value != null) {
 245                 vc = value.getClass();
 246             }
 247         }
 248         if (vc != null) {
 249             Constructor<?> cons;
 250 
 251             try {


 254                 cons = vc.getConstructor(new Class<?>[]{String.class});
 255 
 256             } catch (NoSuchMethodException nsme) {
 257                 cons = null;
 258             }
 259 
 260             if (cons != null) {
 261                 try {
 262                     SwingUtilities2.checkAccess(cons.getModifiers());
 263                     return cons.newInstance(new Object[] { string });
 264                 } catch (Throwable ex) {
 265                     throw new ParseException("Error creating instance", 0);
 266                 }
 267             }
 268         }
 269         return string;
 270     }
 271 
 272     /**
 273      * Converts the passed in Object into a String by way of the
 274      * {@code toString} method.
 275      *
 276      * @throws ParseException if there is an error in the conversion
 277      * @param value Value to convert
 278      * @return String representation of value
 279      */
 280     public String valueToString(Object value) throws ParseException {
 281         if (value == null) {
 282             return "";
 283         }
 284         return value.toString();
 285     }
 286 
 287     /**
 288      * Returns the {@code DocumentFilter} used to restrict the characters
 289      * that can be input into the {@code JFormattedTextField}.
 290      *
 291      * @return DocumentFilter to restrict edits
 292      */
 293     protected DocumentFilter getDocumentFilter() {
 294         if (documentFilter == null) {
 295             documentFilter = new DefaultDocumentFilter();
 296         }
 297         return documentFilter;
 298     }
 299 
 300     /**
 301      * Returns the {@code NavigationFilter} used to restrict where the
 302      * cursor can be placed.
 303      *
 304      * @return NavigationFilter to restrict navigation
 305      */
 306     protected NavigationFilter getNavigationFilter() {
 307         if (navigationFilter == null) {
 308             navigationFilter = new DefaultNavigationFilter();
 309         }
 310         return navigationFilter;
 311     }
 312 
 313     /**
 314      * Creates a copy of the DefaultFormatter.
 315      *
 316      * @return copy of the DefaultFormatter
 317      */
 318     public Object clone() throws CloneNotSupportedException {
 319         DefaultFormatter formatter = (DefaultFormatter)super.clone();
 320 
 321         formatter.navigationFilter = null;
 322         formatter.documentFilter = null;
 323         formatter.replaceHolder = null;
 324         return formatter;
 325     }
 326 
 327 
 328     /**
 329      * Positions the cursor at the initial location.
 330      */
 331     void positionCursorAtInitialLocation() {
 332         JFormattedTextField ftf = getFormattedTextField();
 333         if (ftf != null) {
 334             ftf.setCaretPosition(getInitialVisualPosition());
 335         }
 336     }
 337 
 338     /**
 339      * Returns the initial location to position the cursor at. This forwards
 340      * the call to {@code getNextNavigatableChar}.
 341      */
 342     int getInitialVisualPosition() {
 343         return getNextNavigatableChar(0, 1);
 344     }
 345 
 346     /**
 347      * Subclasses should override this if they want cursor navigation
 348      * to skip certain characters. A return value of false indicates
 349      * the character at {@code offset} should be skipped when
 350      * navigating throught the field.
 351      */
 352     boolean isNavigatable(int offset) {
 353         return true;
 354     }
 355 
 356     /**
 357      * Returns true if the text in {@code text} can be inserted.  This
 358      * does not mean the text will ultimately be inserted, it is used if
 359      * text can trivially reject certain characters.
 360      */
 361     boolean isLegalInsertText(String text) {
 362         return true;
 363     }
 364 
 365     /**
 366      * Returns the next editable character starting at offset incrementing
 367      * the offset by {@code direction}.
 368      */
 369     private int getNextNavigatableChar(int offset, int direction) {
 370         int max = getFormattedTextField().getDocument().getLength();
 371 
 372         while (offset >= 0 && offset < max) {
 373             if (isNavigatable(offset)) {
 374                 return offset;
 375             }
 376             offset += direction;
 377         }
 378         return offset;
 379     }
 380 
 381     /**
 382      * A convenience methods to return the result of deleting
 383      * {@code deleteLength} characters at {@code offset}
 384      * and inserting {@code replaceString} at {@code offset}
 385      * in the current text field.
 386      */
 387     String getReplaceString(int offset, int deleteLength,
 388                             String replaceString) {
 389         String string = getFormattedTextField().getText();
 390         String result;
 391 
 392         result = string.substring(0, offset);
 393         if (replaceString != null) {
 394             result += replaceString;
 395         }
 396         if (offset + deleteLength < string.length()) {
 397             result += string.substring(offset + deleteLength);
 398         }
 399         return result;
 400     }
 401 
 402     /*
 403      * Returns true if the operation described by {@code rh} will
 404      * result in a legal edit.  This may set the {@code value}
 405      * field of {@code rh}.
 406      */
 407     boolean isValidEdit(ReplaceHolder rh) {
 408         if (!getAllowsInvalid()) {
 409             String newString = getReplaceString(rh.offset, rh.length, rh.text);
 410 
 411             try {
 412                 rh.value = stringToValue(newString);
 413 
 414                 return true;
 415             } catch (ParseException pe) {
 416                 return false;
 417             }
 418         }
 419         return true;
 420     }
 421 
 422     /**
 423      * Invokes {@code commitEdit} on the JFormattedTextField.
 424      */
 425     void commitEdit() throws ParseException {
 426         JFormattedTextField ftf = getFormattedTextField();
 427 
 428         if (ftf != null) {
 429             ftf.commitEdit();
 430         }
 431     }
 432 
 433     /**
 434      * Pushes the value to the JFormattedTextField if the current value
 435      * is valid and invokes {@code setEditValid} based on the
 436      * validity of the value.
 437      */
 438     void updateValue() {
 439         updateValue(null);
 440     }
 441 
 442     /**
 443      * Pushes the {@code value} to the editor if we are to
 444      * commit on edits. If {@code value} is null, the current value
 445      * will be obtained from the text component.
 446      */
 447     void updateValue(Object value) {
 448         try {
 449             if (value == null) {
 450                 String string = getFormattedTextField().getText();
 451 
 452                 value = stringToValue(string);
 453             }
 454 
 455             if (getCommitsOnValidEdit()) {
 456                 commitEdit();
 457             }
 458             setEditValid(true);
 459         } catch (ParseException pe) {
 460             setEditValid(false);
 461         }
 462     }
 463 
 464     /**
 465      * Returns the next cursor position from offset by incrementing
 466      * {@code direction}. This uses
 467      * {@code getNextNavigatableChar}
 468      * as well as constraining the location to the max position.
 469      */
 470     int getNextCursorPosition(int offset, int direction) {
 471         int newOffset = getNextNavigatableChar(offset, direction);
 472         int max = getFormattedTextField().getDocument().getLength();
 473 
 474         if (!getAllowsInvalid()) {
 475             if (direction == -1 && offset == newOffset) {
 476                 // Case where hit backspace and only characters before
 477                 // offset are fixed.
 478                 newOffset = getNextNavigatableChar(newOffset, 1);
 479                 if (newOffset >= max) {
 480                     newOffset = offset;
 481                 }
 482             }
 483             else if (direction == 1 && newOffset >= max) {
 484                 // Don't go beyond last editable character.
 485                 newOffset = getNextNavigatableChar(max - 1, -1);
 486                 if (newOffset < max) {
 487                     newOffset++;


 522                 value = text.getUI().getNextVisualPositionFrom(
 523                               text, value, bias, direction,biasRet);
 524             }
 525             int max = getFormattedTextField().getDocument().getLength();
 526             if (last == value || value == max) {
 527                 if (value == 0) {
 528                     biasRet[0] = Position.Bias.Forward;
 529                     value = getInitialVisualPosition();
 530                 }
 531                 if (value >= max && max > 0) {
 532                     // Pending: should not assume forward!
 533                     biasRet[0] = Position.Bias.Forward;
 534                     value = getNextNavigatableChar(max - 1, -1) + 1;
 535                 }
 536             }
 537         }
 538         return value;
 539     }
 540 
 541     /**
 542      * Returns true if the edit described by {@code rh} will result
 543      * in a legal value.
 544      */
 545     boolean canReplace(ReplaceHolder rh) {
 546         return isValidEdit(rh);
 547     }
 548 
 549     /**
 550      * DocumentFilter method, funnels into {@code replace}.
 551      */
 552     void replace(DocumentFilter.FilterBypass fb, int offset,
 553                      int length, String text,
 554                      AttributeSet attrs) throws BadLocationException {
 555         ReplaceHolder rh = getReplaceHolder(fb, offset, length, text, attrs);
 556 
 557         replace(rh);
 558     }
 559 
 560     /**
 561      * If the edit described by {@code rh} is legal, this will
 562      * return true, commit the edit (if necessary) and update the cursor
 563      * position.  This forwards to {@code canReplace} and
 564      * {@code isLegalInsertText} as necessary to determine if
 565      * the edit is in fact legal.
 566      * <p>
 567      * All of the DocumentFilter methods funnel into here, you should
 568      * generally only have to override this.
 569      */
 570     boolean replace(ReplaceHolder rh) throws BadLocationException {
 571         boolean valid = true;
 572         int direction = 1;
 573 
 574         if (rh.length > 0 && (rh.text == null || rh.text.length() == 0) &&
 575                (getFormattedTextField().getSelectionStart() != rh.offset ||
 576                    rh.length > 1)) {
 577             direction = -1;
 578         }
 579 
 580         if (getOverwriteMode() && rh.text != null &&
 581             getFormattedTextField().getSelectedText() == null)
 582         {
 583             rh.length = Math.min(Math.max(rh.length, rh.text.length()),
 584                                  rh.fb.getDocument().getLength() - rh.offset);


< prev index next >