Frames | No Frames |
1: /* DefaultFormatter.java -- 2: Copyright (C) 2005 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: package javax.swing.text; 39: 40: import java.io.Serializable; 41: import java.lang.reflect.Constructor; 42: import java.text.ParseException; 43: 44: import javax.swing.JFormattedTextField; 45: 46: /** 47: * The <code>DefaultFormatter</code> is a concrete formatter for use in 48: * {@link JFormattedTextField}s. 49: * 50: * It can format arbitrary values by invoking 51: * their {@link Object#toString} method. 52: * 53: * In order to convert a String back to 54: * a value, the value class must provide a single argument constructor that 55: * takes a String object as argument value. If no such constructor is found, 56: * the String itself is passed back by #stringToValue. 57: * 58: * @author Roman Kennke (roman@kennke.org) 59: */ 60: public class DefaultFormatter extends JFormattedTextField.AbstractFormatter 61: implements Cloneable, Serializable 62: { 63: 64: /** 65: * A {@link DocumentFilter} that intercepts modification of the 66: * JFormattedTextField's Document and commits the value depending 67: * on the value of the <code>commitsOnValidEdit</code> property. 68: * 69: */ 70: // FIXME: Handle allowsInvalid and overwriteMode properties 71: private class FormatterDocumentFilter 72: extends DocumentFilter 73: { 74: /** 75: * Invoked when text is removed from a text component. 76: * 77: * @param bypass the FilterBypass to use to mutate the document 78: * @param offset the start position of the modification 79: * @param length the length of the removed text 80: * 81: * @throws BadLocationException if offset or lenght are invalid in 82: * the Document 83: */ 84: public void remove(DocumentFilter.FilterBypass bypass, int offset, 85: int length) 86: throws BadLocationException 87: { 88: super.remove(bypass, offset, length); 89: checkValidInput(); 90: commitIfAllowed(); 91: } 92: 93: /** 94: * Invoked when text is inserted into a text component. 95: * 96: * @param bypass the FilterBypass to use to mutate the document 97: * @param offset the start position of the modification 98: * @param text the inserted text 99: * @param attributes the attributes of the inserted text 100: * 101: * @throws BadLocationException if offset or lenght are invalid in 102: * the Document 103: */ 104: public void insertString(DocumentFilter.FilterBypass bypass, int offset, 105: String text, AttributeSet attributes) 106: throws BadLocationException 107: { 108: if (overwriteMode == true) 109: replace(bypass, offset, text.length(), text, attributes); 110: else 111: super.insertString(bypass, offset, text, attributes); 112: checkValidInput(); 113: commitIfAllowed(); 114: } 115: 116: /** 117: * Invoked when text is replaced in a text component. 118: * 119: * @param bypass the FilterBypass to use to mutate the document 120: * @param offset the start position of the modification 121: * @param length the length of the removed text 122: * @param text the inserted text 123: * @param attributes the attributes of the inserted text 124: * 125: * @throws BadLocationException if offset or lenght are invalid in 126: * the Document 127: */ 128: public void replace(DocumentFilter.FilterBypass bypass, int offset, 129: int length, String text, AttributeSet attributes) 130: throws BadLocationException 131: { 132: super.replace(bypass, offset, length, text, attributes); 133: checkValidInput(); 134: commitIfAllowed(); 135: } 136: 137: /** 138: * Commits the value to the JTextTextField if the property 139: * <code>commitsOnValidEdit</code> is set to <code>true</code>. 140: */ 141: private void commitIfAllowed() 142: { 143: if (commitsOnValidEdit == true) 144: try 145: { 146: getFormattedTextField().commitEdit(); 147: } 148: catch (ParseException ex) 149: { 150: // ignore invalid edits 151: } 152: } 153: 154: /** 155: * Checks if the value in the input field is valid. If the 156: * property allowsInvalid is set to <code>false</code>, then 157: * the string in the input field is not allowed to be entered. 158: */ 159: private void checkValidInput() 160: { 161: JFormattedTextField ftf = getFormattedTextField(); 162: try 163: { 164: Object newval = stringToValue(ftf.getText()); 165: } 166: catch (ParseException ex) 167: { 168: if (!allowsInvalid) 169: { 170: // roll back the input if invalid edits are not allowed 171: try 172: { 173: ftf.setText(valueToString(ftf.getValue())); 174: } 175: catch (ParseException pe) 176: { 177: // if that happens, something serious must be wrong 178: AssertionError ae; 179: ae = new AssertionError("values must be parseable"); 180: ae.initCause(pe); 181: throw ae; 182: } 183: } 184: } 185: } 186: } 187: 188: /** The serialization UID (compatible with JDK1.5). */ 189: private static final long serialVersionUID = -355018354457785329L; 190: 191: /** 192: * Indicates if the value should be committed after every 193: * valid modification of the Document. 194: */ 195: boolean commitsOnValidEdit; 196: 197: /** 198: * If <code>true</code> newly inserted characters overwrite existing 199: * values, otherwise insertion is done the normal way. 200: */ 201: boolean overwriteMode; 202: 203: /** 204: * If <code>true</code> invalid edits are allowed for a limited 205: * time. 206: */ 207: boolean allowsInvalid; 208: 209: /** 210: * The class that is used for values. 211: */ 212: Class valueClass; 213: 214: /** 215: * Creates a new instance of <code>DefaultFormatter</code>. 216: */ 217: public DefaultFormatter() 218: { 219: commitsOnValidEdit = false; 220: overwriteMode = true; 221: allowsInvalid = true; 222: } 223: 224: /** 225: * Installs the formatter on the specified {@link JFormattedTextField}. 226: * 227: * This method does the following things: 228: * <ul> 229: * <li>Display the value of #valueToString in the 230: * <code>JFormattedTextField</code></li> 231: * <li>Install the Actions from #getActions on the <code>JTextField</code> 232: * </li> 233: * <li>Install the DocumentFilter returned by #getDocumentFilter</li> 234: * <li>Install the NavigationFilter returned by #getNavigationFilter</li> 235: * </ul> 236: * 237: * This method is typically not overridden by subclasses. Instead override 238: * one of the mentioned methods in order to customize behaviour. 239: * 240: * @param ftf the {@link JFormattedTextField} in which this formatter 241: * is installed 242: */ 243: public void install(JFormattedTextField ftf) 244: { 245: super.install(ftf); 246: } 247: 248: /** 249: * Returns <code>true</code> if the value should be committed after 250: * each valid modification of the input field, <code>false</code> if 251: * it should never be committed by this formatter. 252: * 253: * @return the state of the <code>commitsOnValidEdit</code> property 254: * 255: * @see #setCommitsOnValidEdit 256: */ 257: public boolean getCommitsOnValidEdit() 258: { 259: return commitsOnValidEdit; 260: } 261: 262: /** 263: * Sets the value of the <code>commitsOnValidEdit</code> property. 264: * 265: * @param commitsOnValidEdit the new state of the 266: * <code>commitsOnValidEdit</code> property 267: * 268: * @see #getCommitsOnValidEdit 269: */ 270: public void setCommitsOnValidEdit(boolean commitsOnValidEdit) 271: { 272: this.commitsOnValidEdit = commitsOnValidEdit; 273: } 274: 275: /** 276: * Returns the value of the <code>overwriteMode</code> property. 277: * If that is set to <code>true</code> then newly inserted characters 278: * overwrite existing values, otherwise the characters are inserted like 279: * normal. The default is <code>true</code>. 280: * 281: * @return the value of the <code>overwriteMode</code> property 282: */ 283: public boolean getOverwriteMode() 284: { 285: return overwriteMode; 286: } 287: 288: /** 289: * Sets the value of the <code>overwriteMode</code> property. 290: * 291: * If that is set to <code>true</code> then newly inserted characters 292: * overwrite existing values, otherwise the characters are inserted like 293: * normal. The default is <code>true</code>. 294: * 295: * @param overwriteMode the new value for the <code>overwriteMode</code> 296: * property 297: */ 298: public void setOverwriteMode(boolean overwriteMode) 299: { 300: this.overwriteMode = overwriteMode; 301: } 302: 303: /** 304: * Returns whether or not invalid edits are allowed or not. If invalid 305: * edits are allowed, the JFormattedTextField may temporarily contain invalid 306: * characters. 307: * 308: * @return the value of the allowsInvalid property 309: */ 310: public boolean getAllowsInvalid() 311: { 312: return allowsInvalid; 313: } 314: 315: /** 316: * Sets the value of the <code>allowsInvalid</code> property. 317: * 318: * @param allowsInvalid the new value for the property 319: * 320: * @see #getAllowsInvalid() 321: */ 322: public void setAllowsInvalid(boolean allowsInvalid) 323: { 324: this.allowsInvalid = allowsInvalid; 325: } 326: 327: /** 328: * Returns the class that is used for values. When Strings are converted 329: * back to values, this class is used to create new value objects. 330: * 331: * @return the class that is used for values 332: */ 333: public Class<?> getValueClass() 334: { 335: return valueClass; 336: } 337: 338: /** 339: * Sets the class that is used for values. 340: * 341: * @param valueClass the class that is used for values 342: * 343: * @see #getValueClass() 344: */ 345: public void setValueClass(Class<?> valueClass) 346: { 347: this.valueClass = valueClass; 348: } 349: 350: /** 351: * Converts a String (from the JFormattedTextField input) to a value. 352: * In order to achieve this, the formatter tries to instantiate an object 353: * of the class returned by #getValueClass() using a single argument 354: * constructor that takes a String argument. If such a constructor cannot 355: * be found, the String itself is returned. 356: * 357: * @param string the string to convert 358: * 359: * @return the value for the string 360: * 361: * @throws ParseException if the string cannot be converted into 362: * a value object (e.g. invalid input) 363: */ 364: public Object stringToValue(String string) 365: throws ParseException 366: { 367: Object value = string; 368: Class valueClass = getValueClass(); 369: if (valueClass == null) 370: { 371: JFormattedTextField jft = getFormattedTextField(); 372: if (jft != null) 373: valueClass = jft.getValue().getClass(); 374: } 375: if (valueClass != null) 376: try 377: { 378: Constructor constr = valueClass.getConstructor 379: (new Class[]{String.class}); 380: value = constr.newInstance(new Object[]{ string }); 381: } 382: catch (NoSuchMethodException ex) 383: { 384: // leave value as string 385: } 386: catch (Exception ex) 387: { 388: throw new ParseException(string, 0); 389: } 390: return value; 391: } 392: 393: /** 394: * Converts a value object into a String. This is done by invoking the 395: * {@link Object#toString()} method on the value. 396: * 397: * @param value the value to be converted 398: * 399: * @return the string representation of the value 400: * 401: * @throws ParseException if the value cannot be converted 402: */ 403: public String valueToString(Object value) 404: throws ParseException 405: { 406: if (value == null) 407: return ""; 408: return value.toString(); 409: } 410: 411: /** 412: * Creates and returns a clone of this DefaultFormatter. 413: * 414: * @return a clone of this object 415: * 416: * @throws CloneNotSupportedException not thrown here 417: */ 418: public Object clone() 419: throws CloneNotSupportedException 420: { 421: return super.clone(); 422: } 423: 424: /** 425: * Returns the DocumentFilter that is used to restrict input. 426: * 427: * @return the DocumentFilter that is used to restrict input 428: */ 429: protected DocumentFilter getDocumentFilter() 430: { 431: return new FormatterDocumentFilter(); 432: } 433: }