001/* DefaultFormatter.java -- 002Copyright (C) 2005 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038package javax.swing.text; 039 040import java.io.Serializable; 041import java.lang.reflect.Constructor; 042import java.text.ParseException; 043 044import javax.swing.JFormattedTextField; 045 046/** 047 * The <code>DefaultFormatter</code> is a concrete formatter for use in 048 * {@link JFormattedTextField}s. 049 * 050 * It can format arbitrary values by invoking 051 * their {@link Object#toString} method. 052 * 053 * In order to convert a String back to 054 * a value, the value class must provide a single argument constructor that 055 * takes a String object as argument value. If no such constructor is found, 056 * the String itself is passed back by #stringToValue. 057 * 058 * @author Roman Kennke (roman@kennke.org) 059 */ 060public class DefaultFormatter extends JFormattedTextField.AbstractFormatter 061 implements Cloneable, Serializable 062{ 063 064 /** 065 * A {@link DocumentFilter} that intercepts modification of the 066 * JFormattedTextField's Document and commits the value depending 067 * on the value of the <code>commitsOnValidEdit</code> property. 068 * 069 */ 070 // FIXME: Handle allowsInvalid and overwriteMode properties 071 private class FormatterDocumentFilter 072 extends DocumentFilter 073 { 074 /** 075 * Invoked when text is removed from a text component. 076 * 077 * @param bypass the FilterBypass to use to mutate the document 078 * @param offset the start position of the modification 079 * @param length the length of the removed text 080 * 081 * @throws BadLocationException if offset or lenght are invalid in 082 * the Document 083 */ 084 public void remove(DocumentFilter.FilterBypass bypass, int offset, 085 int length) 086 throws BadLocationException 087 { 088 super.remove(bypass, offset, length); 089 checkValidInput(); 090 commitIfAllowed(); 091 } 092 093 /** 094 * Invoked when text is inserted into a text component. 095 * 096 * @param bypass the FilterBypass to use to mutate the document 097 * @param offset the start position of the modification 098 * @param text the inserted text 099 * @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}