| /* | |
| * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package javax.swing.text; | |
| import sun.swing.SwingUtilities2; | |
| import java.io.Serializable; | |
| import java.lang.reflect.*; | |
| import java.text.ParseException; | |
| import javax.swing.*; | |
| /** | |
| * <code>DefaultFormatter</code> formats arbitrary objects. Formatting is done | |
| * by invoking the <code>toString</code> method. In order to convert the | |
| * value back to a String, your class must provide a constructor that | |
| * takes a String argument. If no single argument constructor that takes a | |
| * String is found, the returned value will be the String passed into | |
| * <code>stringToValue</code>. | |
| * <p> | |
| * Instances of <code>DefaultFormatter</code> can not be used in multiple | |
| * instances of <code>JFormattedTextField</code>. To obtain a copy of | |
| * an already configured <code>DefaultFormatter</code>, use the | |
| * <code>clone</code> method. | |
| * <p> | |
| * <strong>Warning:</strong> | |
| * Serialized objects of this class will not be compatible with | |
| * future Swing releases. The current serialization support is | |
| * appropriate for short term storage or RMI between applications running | |
| * the same version of Swing. As of 1.4, support for long term storage | |
| * of all JavaBeans | |
| * has been added to the <code>java.beans</code> package. | |
| * Please see {@link java.beans.XMLEncoder}. | |
| * | |
| * @see javax.swing.JFormattedTextField.AbstractFormatter | |
| * | |
| * @since 1.4 | |
| */ | |
| @SuppressWarnings("serial") // Same-version serialization only | |
| public class DefaultFormatter extends JFormattedTextField.AbstractFormatter | |
| implements Cloneable, Serializable { | |
| /** Indicates if the value being edited must match the mask. */ | |
| private boolean allowsInvalid; | |
| /** If true, overwrite mode is enabled. */ | |
| private boolean overwriteMode; | |
| /** If true, any time a valid edit happens commitEdit is invoked. */ | |
| private boolean commitOnEdit; | |
| /** Class used to create new instances. */ | |
| private Class<?> valueClass; | |
| /** NavigationFilter that forwards calls back to DefaultFormatter. */ | |
| private NavigationFilter navigationFilter; | |
| /** DocumentFilter that forwards calls back to DefaultFormatter. */ | |
| private DocumentFilter documentFilter; | |
| /** Used during replace to track the region to replace. */ | |
| transient ReplaceHolder replaceHolder; | |
| /** | |
| * Creates a DefaultFormatter. | |
| */ | |
| public DefaultFormatter() { | |
| overwriteMode = true; | |
| allowsInvalid = true; | |
| } | |
| /** | |
| * Installs the <code>DefaultFormatter</code> onto a particular | |
| * <code>JFormattedTextField</code>. | |
| * This will invoke <code>valueToString</code> to convert the | |
| * current value from the <code>JFormattedTextField</code> to | |
| * a String. This will then install the <code>Action</code>s from | |
| * <code>getActions</code>, the <code>DocumentFilter</code> | |
| * returned from <code>getDocumentFilter</code> and the | |
| * <code>NavigationFilter</code> returned from | |
| * <code>getNavigationFilter</code> onto the | |
| * <code>JFormattedTextField</code>. | |
| * <p> | |
| * Subclasses will typically only need to override this if they | |
| * wish to install additional listeners on the | |
| * <code>JFormattedTextField</code>. | |
| * <p> | |
| * If there is a <code>ParseException</code> in converting the | |
| * current value to a String, this will set the text to an empty | |
| * String, and mark the <code>JFormattedTextField</code> as being | |
| * in an invalid state. | |
| * <p> | |
| * While this is a public method, this is typically only useful | |
| * for subclassers of <code>JFormattedTextField</code>. | |
| * <code>JFormattedTextField</code> will invoke this method at | |
| * the appropriate times when the value changes, or its internal | |
| * state changes. | |
| * | |
| * @param ftf JFormattedTextField to format for, may be null indicating | |
| * uninstall from current JFormattedTextField. | |
| */ | |
| public void install(JFormattedTextField ftf) { | |
| super.install(ftf); | |
| positionCursorAtInitialLocation(); | |
| } | |
| /** | |
| * Sets when edits are published back to the | |
| * <code>JFormattedTextField</code>. If true, <code>commitEdit</code> | |
| * is invoked after every valid edit (any time the text is edited). On | |
| * the other hand, if this is false than the <code>DefaultFormatter</code> | |
| * does not publish edits back to the <code>JFormattedTextField</code>. | |
| * As such, the only time the value of the <code>JFormattedTextField</code> | |
| * will change is when <code>commitEdit</code> is invoked on | |
| * <code>JFormattedTextField</code>, typically when enter is pressed | |
| * or focus leaves the <code>JFormattedTextField</code>. | |
| * | |
| * @param commit Used to indicate when edits are committed back to the | |
| * JTextComponent | |
| */ | |
| public void setCommitsOnValidEdit(boolean commit) { | |
| commitOnEdit = commit; | |
| } | |
| /** | |
| * Returns when edits are published back to the | |
| * <code>JFormattedTextField</code>. | |
| * | |
| * @return true if edits are committed after every valid edit | |
| */ | |
| public boolean getCommitsOnValidEdit() { | |
| return commitOnEdit; | |
| } | |
| /** | |
| * Configures the behavior when inserting characters. If | |
| * <code>overwriteMode</code> is true (the default), new characters | |
| * overwrite existing characters in the model. | |
| * | |
| * @param overwriteMode Indicates if overwrite mode is used | |
| */ | |
| public void setOverwriteMode(boolean overwriteMode) { | |
| this.overwriteMode = overwriteMode; | |
| } | |
| /** | |
| * Returns the behavior when inserting characters. | |
| * | |
| * @return true if newly inserted characters overwrite existing characters | |
| */ | |
| public boolean getOverwriteMode() { | |
| return overwriteMode; | |
| } | |
| /** | |
| * Sets whether or not the value being edited is allowed to be invalid | |
| * for a length of time (that is, <code>stringToValue</code> throws | |
| * a <code>ParseException</code>). | |
| * It is often convenient to allow the user to temporarily input an | |
| * invalid value. | |
| * | |
| * @param allowsInvalid Used to indicate if the edited value must always | |
| * be valid | |
| */ | |
| public void setAllowsInvalid(boolean allowsInvalid) { | |
| this.allowsInvalid = allowsInvalid; | |
| } | |
| /** | |
| * Returns whether or not the value being edited is allowed to be invalid | |
| * for a length of time. | |
| * | |
| * @return false if the edited value must always be valid | |
| */ | |
| public boolean getAllowsInvalid() { | |
| return allowsInvalid; | |
| } | |
| /** | |
| * Sets that class that is used to create new Objects. If the | |
| * passed in class does not have a single argument constructor that | |
| * takes a String, String values will be used. | |
| * | |
| * @param valueClass Class used to construct return value from | |
| * stringToValue | |
| */ | |
| public void setValueClass(Class<?> valueClass) { | |
| this.valueClass = valueClass; | |
| } | |
| /** | |
| * Returns that class that is used to create new Objects. | |
| * | |
| * @return Class used to construct return value from stringToValue | |
| */ | |
| public Class<?> getValueClass() { | |
| return valueClass; | |
| } | |
| /** | |
| * Converts the passed in String into an instance of | |
| * <code>getValueClass</code> by way of the constructor that | |
| * takes a String argument. If <code>getValueClass</code> | |
| * returns null, the Class of the current value in the | |
| * <code>JFormattedTextField</code> will be used. If this is null, a | |
| * String will be returned. If the constructor throws an exception, a | |
| * <code>ParseException</code> will be thrown. If there is no single | |
| * argument String constructor, <code>string</code> will be returned. | |
| * | |
| * @throws ParseException if there is an error in the conversion | |
| * @param string String to convert | |
| * @return Object representation of text | |
| */ | |
| public Object stringToValue(String string) throws ParseException { | |
| Class<?> vc = getValueClass(); | |
| JFormattedTextField ftf = getFormattedTextField(); | |
| if (vc == null && ftf != null) { | |
| Object value = ftf.getValue(); | |
| if (value != null) { | |
| vc = value.getClass(); | |
| } | |
| } | |
| if (vc != null) { | |
| Constructor<?> cons; | |
| try { | |
| cons = vc.getConstructor(new Class<?>[]{String.class}); | |
| } catch (NoSuchMethodException nsme) { | |
| cons = null; | |
| } | |
| if (cons != null) { | |
| try { | |
| return cons.newInstance(new Object[] { string }); | |
| } catch (Throwable ex) { | |
| throw new ParseException("Error creating instance", 0); | |
| } | |
| } | |
| } | |
| return string; | |
| } | |
| /** | |
| * Converts the passed in Object into a String by way of the | |
| * <code>toString</code> method. | |
| * | |
| * @throws ParseException if there is an error in the conversion | |
| * @param value Value to convert | |
| * @return String representation of value | |
| */ | |
| public String valueToString(Object value) throws ParseException { | |
| if (value == null) { | |
| return ""; | |
| } | |
| return value.toString(); | |
| } | |
| /** | |
| * Returns the <code>DocumentFilter</code> used to restrict the characters | |
| * that can be input into the <code>JFormattedTextField</code>. | |
| * | |
| * @return DocumentFilter to restrict edits | |
| */ | |
| protected DocumentFilter getDocumentFilter() { | |
| if (documentFilter == null) { | |
| documentFilter = new DefaultDocumentFilter(); | |
| } | |
| return documentFilter; | |
| } | |
| /** | |
| * Returns the <code>NavigationFilter</code> used to restrict where the | |
| * cursor can be placed. | |
| * | |
| * @return NavigationFilter to restrict navigation | |
| */ | |
| protected NavigationFilter getNavigationFilter() { | |
| if (navigationFilter == null) { | |
| navigationFilter = new DefaultNavigationFilter(); | |
| } | |
| return navigationFilter; | |
| } | |
| /** | |
| * Creates a copy of the DefaultFormatter. | |
| * | |
| * @return copy of the DefaultFormatter | |
| */ | |
| public Object clone() throws CloneNotSupportedException { | |
| DefaultFormatter formatter = (DefaultFormatter)super.clone(); | |
| formatter.navigationFilter = null; | |
| formatter.documentFilter = null; | |
| formatter.replaceHolder = null; | |
| return formatter; | |
| } | |
| /** | |
| * Positions the cursor at the initial location. | |
| */ | |
| void positionCursorAtInitialLocation() { | |
| JFormattedTextField ftf = getFormattedTextField(); | |
| if (ftf != null) { | |
| ftf.setCaretPosition(getInitialVisualPosition()); | |
| } | |
| } | |
| /** | |
| * Returns the initial location to position the cursor at. This forwards | |
| * the call to <code>getNextNavigatableChar</code>. | |
| */ | |
| int getInitialVisualPosition() { | |
| return getNextNavigatableChar(0, 1); | |
| } | |
| /** | |
| * Subclasses should override this if they want cursor navigation | |
| * to skip certain characters. A return value of false indicates | |
| * the character at <code>offset</code> should be skipped when | |
| * navigating through the field. | |
| */ | |
| boolean isNavigatable(int offset) { | |
| return true; | |
| } | |
| /** | |
| * Returns true if the text in <code>text</code> can be inserted. This | |
| * does not mean the text will ultimately be inserted, it is used if | |
| * text can trivially reject certain characters. | |
| */ | |
| boolean isLegalInsertText(String text) { | |
| return true; | |
| } | |
| /** | |
| * Returns the next editable character starting at offset incrementing | |
| * the offset by <code>direction</code>. | |
| */ | |
| private int getNextNavigatableChar(int offset, int direction) { | |
| int max = getFormattedTextField().getDocument().getLength(); | |
| while (offset >= 0 && offset < max) { | |
| if (isNavigatable(offset)) { | |
| return offset; | |
| } | |
| offset += direction; | |
| } | |
| return offset; | |
| } | |
| /** | |
| * A convenience methods to return the result of deleting | |
| * <code>deleteLength</code> characters at <code>offset</code> | |
| * and inserting <code>replaceString</code> at <code>offset</code> | |
| * in the current text field. | |
| */ | |
| String getReplaceString(int offset, int deleteLength, | |
| String replaceString) { | |
| String string = getFormattedTextField().getText(); | |
| String result; | |
| result = string.substring(0, offset); | |
| if (replaceString != null) { | |
| result += replaceString; | |
| } | |
| if (offset + deleteLength < string.length()) { | |
| result += string.substring(offset + deleteLength); | |
| } | |
| return result; | |
| } | |
| /* | |
| * Returns true if the operation described by <code>rh</code> will | |
| * result in a legal edit. This may set the <code>value</code> | |
| * field of <code>rh</code>. | |
| */ | |
| boolean isValidEdit(ReplaceHolder rh) { | |
| if (!getAllowsInvalid()) { | |
| String newString = getReplaceString(rh.offset, rh.length, rh.text); | |
| try { | |
| rh.value = stringToValue(newString); | |
| return true; | |
| } catch (ParseException pe) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| /** | |
| * Invokes <code>commitEdit</code> on the JFormattedTextField. | |
| */ | |
| void commitEdit() throws ParseException { | |
| JFormattedTextField ftf = getFormattedTextField(); | |
| if (ftf != null) { | |
| ftf.commitEdit(); | |
| } | |
| } | |
| /** | |
| * Pushes the value to the JFormattedTextField if the current value | |
| * is valid and invokes <code>setEditValid</code> based on the | |
| * validity of the value. | |
| */ | |
| void updateValue() { | |
| updateValue(null); | |
| } | |
| /** | |
| * Pushes the <code>value</code> to the editor if we are to | |
| * commit on edits. If <code>value</code> is null, the current value | |
| * will be obtained from the text component. | |
| */ | |
| void updateValue(Object value) { | |
| try { | |
| if (value == null) { | |
| String string = getFormattedTextField().getText(); | |
| value = stringToValue(string); | |
| } | |
| if (getCommitsOnValidEdit()) { | |
| commitEdit(); | |
| } | |
| setEditValid(true); | |
| } catch (ParseException pe) { | |
| setEditValid(false); | |
| } | |
| } | |
| /** | |
| * Returns the next cursor position from offset by incrementing | |
| * <code>direction</code>. This uses | |
| * <code>getNextNavigatableChar</code> | |
| * as well as constraining the location to the max position. | |
| */ | |
| int getNextCursorPosition(int offset, int direction) { | |
| int newOffset = getNextNavigatableChar(offset, direction); | |
| int max = getFormattedTextField().getDocument().getLength(); | |
| if (!getAllowsInvalid()) { | |
| if (direction == -1 && offset == newOffset) { | |
| // Case where hit backspace and only characters before | |
| // offset are fixed. | |
| newOffset = getNextNavigatableChar(newOffset, 1); | |
| if (newOffset >= max) { | |
| newOffset = offset; | |
| } | |
| } | |
| else if (direction == 1 && newOffset >= max) { | |
| // Don't go beyond last editable character. | |
| newOffset = getNextNavigatableChar(max - 1, -1); | |
| if (newOffset < max) { | |
| newOffset++; | |
| } | |
| } | |
| } | |
| return newOffset; | |
| } | |
| /** | |
| * Resets the cursor by using getNextCursorPosition. | |
| */ | |
| void repositionCursor(int offset, int direction) { | |
| getFormattedTextField().getCaret().setDot(getNextCursorPosition | |
| (offset, direction)); | |
| } | |
| /** | |
| * Finds the next navigable character. | |
| */ | |
| int getNextVisualPositionFrom(JTextComponent text, int pos, | |
| Position.Bias bias, int direction, | |
| Position.Bias[] biasRet) | |
| throws BadLocationException { | |
| int value = text.getUI().getNextVisualPositionFrom(text, pos, bias, | |
| direction, biasRet); | |
| if (value == -1) { | |
| return -1; | |
| } | |
| if (!getAllowsInvalid() && (direction == SwingConstants.EAST || | |
| direction == SwingConstants.WEST)) { | |
| int last = -1; | |
| while (!isNavigatable(value) && value != last) { | |
| last = value; | |
| value = text.getUI().getNextVisualPositionFrom( | |
| text, value, bias, direction,biasRet); | |
| } | |
| int max = getFormattedTextField().getDocument().getLength(); | |
| if (last == value || value == max) { | |
| if (value == 0) { | |
| biasRet[0] = Position.Bias.Forward; | |
| value = getInitialVisualPosition(); | |
| } | |
| if (value >= max && max > 0) { | |
| // Pending: should not assume forward! | |
| biasRet[0] = Position.Bias.Forward; | |
| value = getNextNavigatableChar(max - 1, -1) + 1; | |
| } | |
| } | |
| } | |
| return value; | |
| } | |
| /** | |
| * Returns true if the edit described by <code>rh</code> will result | |
| * in a legal value. | |
| */ | |
| boolean canReplace(ReplaceHolder rh) { | |
| return isValidEdit(rh); | |
| } | |
| /** | |
| * DocumentFilter method, funnels into <code>replace</code>. | |
| */ | |
| void replace(DocumentFilter.FilterBypass fb, int offset, | |
| int length, String text, | |
| AttributeSet attrs) throws BadLocationException { | |
| ReplaceHolder rh = getReplaceHolder(fb, offset, length, text, attrs); | |
| replace(rh); | |
| } | |
| /** | |
| * If the edit described by <code>rh</code> is legal, this will | |
| * return true, commit the edit (if necessary) and update the cursor | |
| * position. This forwards to <code>canReplace</code> and | |
| * <code>isLegalInsertText</code> as necessary to determine if | |
| * the edit is in fact legal. | |
| * <p> | |
| * All of the DocumentFilter methods funnel into here, you should | |
| * generally only have to override this. | |
| */ | |
| boolean replace(ReplaceHolder rh) throws BadLocationException { | |
| boolean valid = true; | |
| int direction = 1; | |
| if (rh.length > 0 && (rh.text == null || rh.text.length() == 0) && | |
| (getFormattedTextField().getSelectionStart() != rh.offset || | |
| rh.length > 1)) { | |
| direction = -1; | |
| } | |
| if (getOverwriteMode() && rh.text != null && | |
| getFormattedTextField().getSelectedText() == null) | |
| { | |
| rh.length = Math.min(Math.max(rh.length, rh.text.length()), | |
| rh.fb.getDocument().getLength() - rh.offset); | |
| } | |
| if ((rh.text != null && !isLegalInsertText(rh.text)) || | |
| !canReplace(rh) || | |
| (rh.length == 0 && (rh.text == null || rh.text.length() == 0))) { | |
| valid = false; | |
| } | |
| if (valid) { | |
| int cursor = rh.cursorPosition; | |
| rh.fb.replace(rh.offset, rh.length, rh.text, rh.attrs); | |
| if (cursor == -1) { | |
| cursor = rh.offset; | |
| if (direction == 1 && rh.text != null) { | |
| cursor = rh.offset + rh.text.length(); | |
| } | |
| } | |
| updateValue(rh.value); | |
| repositionCursor(cursor, direction); | |
| return true; | |
| } | |
| else { | |
| invalidEdit(); | |
| } | |
| return false; | |
| } | |
| /** | |
| * NavigationFilter method, subclasses that wish finer control should | |
| * override this. | |
| */ | |
| void setDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias){ | |
| fb.setDot(dot, bias); | |
| } | |
| /** | |
| * NavigationFilter method, subclasses that wish finer control should | |
| * override this. | |
| */ | |
| void moveDot(NavigationFilter.FilterBypass fb, int dot, | |
| Position.Bias bias) { | |
| fb.moveDot(dot, bias); | |
| } | |
| /** | |
| * Returns the ReplaceHolder to track the replace of the specified | |
| * text. | |
| */ | |
| ReplaceHolder getReplaceHolder(DocumentFilter.FilterBypass fb, int offset, | |
| int length, String text, | |
| AttributeSet attrs) { | |
| if (replaceHolder == null) { | |
| replaceHolder = new ReplaceHolder(); | |
| } | |
| replaceHolder.reset(fb, offset, length, text, attrs); | |
| return replaceHolder; | |
| } | |
| /** | |
| * ReplaceHolder is used to track where insert/remove/replace is | |
| * going to happen. | |
| */ | |
| static class ReplaceHolder { | |
| /** The FilterBypass that was passed to the DocumentFilter method. */ | |
| DocumentFilter.FilterBypass fb; | |
| /** Offset where the remove/insert is going to occur. */ | |
| int offset; | |
| /** Length of text to remove. */ | |
| int length; | |
| /** The text to insert, may be null. */ | |
| String text; | |
| /** AttributeSet to attach to text, may be null. */ | |
| AttributeSet attrs; | |
| /** The resulting value, this may never be set. */ | |
| Object value; | |
| /** Position the cursor should be adjusted from. If this is -1 | |
| * the cursor position will be adjusted based on the direction of | |
| * the replace (-1: offset, 1: offset + text.length()), otherwise | |
| * the cursor position is adjusted from this position. | |
| */ | |
| int cursorPosition; | |
| void reset(DocumentFilter.FilterBypass fb, int offset, int length, | |
| String text, AttributeSet attrs) { | |
| this.fb = fb; | |
| this.offset = offset; | |
| this.length = length; | |
| this.text = text; | |
| this.attrs = attrs; | |
| this.value = null; | |
| cursorPosition = -1; | |
| } | |
| } | |
| /** | |
| * NavigationFilter implementation that calls back to methods with | |
| * same name in DefaultFormatter. | |
| */ | |
| private class DefaultNavigationFilter extends NavigationFilter | |
| implements Serializable { | |
| public void setDot(FilterBypass fb, int dot, Position.Bias bias) { | |
| JTextComponent tc = DefaultFormatter.this.getFormattedTextField(); | |
| if (tc.composedTextExists()) { | |
| // bypass the filter | |
| fb.setDot(dot, bias); | |
| } else { | |
| DefaultFormatter.this.setDot(fb, dot, bias); | |
| } | |
| } | |
| public void moveDot(FilterBypass fb, int dot, Position.Bias bias) { | |
| JTextComponent tc = DefaultFormatter.this.getFormattedTextField(); | |
| if (tc.composedTextExists()) { | |
| // bypass the filter | |
| fb.moveDot(dot, bias); | |
| } else { | |
| DefaultFormatter.this.moveDot(fb, dot, bias); | |
| } | |
| } | |
| public int getNextVisualPositionFrom(JTextComponent text, int pos, | |
| Position.Bias bias, | |
| int direction, | |
| Position.Bias[] biasRet) | |
| throws BadLocationException { | |
| if (text.composedTextExists()) { | |
| // forward the call to the UI directly | |
| return text.getUI().getNextVisualPositionFrom( | |
| text, pos, bias, direction, biasRet); | |
| } else { | |
| return DefaultFormatter.this.getNextVisualPositionFrom( | |
| text, pos, bias, direction, biasRet); | |
| } | |
| } | |
| } | |
| /** | |
| * DocumentFilter implementation that calls back to the replace | |
| * method of DefaultFormatter. | |
| */ | |
| private class DefaultDocumentFilter extends DocumentFilter implements | |
| Serializable { | |
| public void remove(FilterBypass fb, int offset, int length) throws | |
| BadLocationException { | |
| JTextComponent tc = DefaultFormatter.this.getFormattedTextField(); | |
| if (tc.composedTextExists()) { | |
| // bypass the filter | |
| fb.remove(offset, length); | |
| } else { | |
| DefaultFormatter.this.replace(fb, offset, length, null, null); | |
| } | |
| } | |
| public void insertString(FilterBypass fb, int offset, | |
| String string, AttributeSet attr) throws | |
| BadLocationException { | |
| JTextComponent tc = DefaultFormatter.this.getFormattedTextField(); | |
| if (tc.composedTextExists() || | |
| Utilities.isComposedTextAttributeDefined(attr)) { | |
| // bypass the filter | |
| fb.insertString(offset, string, attr); | |
| } else { | |
| DefaultFormatter.this.replace(fb, offset, 0, string, attr); | |
| } | |
| } | |
| public void replace(FilterBypass fb, int offset, int length, | |
| String text, AttributeSet attr) throws | |
| BadLocationException { | |
| JTextComponent tc = DefaultFormatter.this.getFormattedTextField(); | |
| if (tc.composedTextExists() || | |
| Utilities.isComposedTextAttributeDefined(attr)) { | |
| // bypass the filter | |
| fb.replace(offset, length, text, attr); | |
| } else { | |
| DefaultFormatter.this.replace(fb, offset, length, text, attr); | |
| } | |
| } | |
| } | |
| } |