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™ 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™ 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); |