Source Code Cross Referenced for InternationalFormatter.java in  » 6.0-JDK-Core » swing » javax » swing » text » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Home
Java Source Code / Java Documentation
1.6.0 JDK Core
2.6.0 JDK Modules
3.6.0 JDK Modules com.sun
4.6.0 JDK Modules com.sun.java
5.6.0 JDK Modules sun
6.6.0 JDK Platform
7.Ajax
8.Apache Harmony Java SE
9.Aspect oriented
10.Authentication Authorization
11.Blogger System
12.Build
13.Byte Code
14.Cache
15.Chart
16.Chat
17.Code Analyzer
18.Collaboration
19.Content Management System
20.Database Client
21.Database DBMS
22.Database JDBC Connection Pool
23.Database ORM
24.Development
25.EJB Server
26.ERP CRM Financial
27.ESB
28.Forum
29.Game
30.GIS
31.Graphic 3D
32.Graphic Library
33.Groupware
34.HTML Parser
35.IDE
36.IDE Eclipse
37.IDE Netbeans
38.Installer
39.Internationalization Localization
40.Inversion of Control
41.Issue Tracking
42.J2EE
43.J2ME
44.JBoss
45.JMS
46.JMX
47.Library
48.Mail Clients
49.Music
50.Net
51.Parser
52.PDF
53.Portal
54.Profiler
55.Project Management
56.Report
57.RSS RDF
58.Rule Engine
59.Science
60.Scripting
61.Search Engine
62.Security
63.Sevlet Container
64.Source Control
65.Swing Library
66.Template Engine
67.Test Coverage
68.Testing
69.UML
70.Web Crawler
71.Web Framework
72.Web Mail
73.Web Server
74.Web Services
75.Web Services apache cxf 2.2.6
76.Web Services AXIS2
77.Wiki Engine
78.Workflow Engines
79.XML
80.XML UI
Java Source Code / Java Documentation » 6.0 JDK Core » swing » javax.swing.text 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001        /*
0002         * Copyright 2000-2006 Sun Microsystems, Inc.  All Rights Reserved.
0003         * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004         *
0005         * This code is free software; you can redistribute it and/or modify it
0006         * under the terms of the GNU General Public License version 2 only, as
0007         * published by the Free Software Foundation.  Sun designates this
0008         * particular file as subject to the "Classpath" exception as provided
0009         * by Sun in the LICENSE file that accompanied this code.
0010         *
0011         * This code is distributed in the hope that it will be useful, but WITHOUT
0012         * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013         * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
0014         * version 2 for more details (a copy is included in the LICENSE file that
0015         * accompanied this code).
0016         *
0017         * You should have received a copy of the GNU General Public License version
0018         * 2 along with this work; if not, write to the Free Software Foundation,
0019         * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020         *
0021         * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022         * CA 95054 USA or visit www.sun.com if you need additional information or
0023         * have any questions.
0024         */
0025        package javax.swing.text;
0026
0027        import java.awt.event.ActionEvent;
0028        import java.io.*;
0029        import java.text.*;
0030        import java.util.*;
0031        import javax.swing.*;
0032        import javax.swing.text.*;
0033
0034        /**
0035         * <code>InternationalFormatter</code> extends <code>DefaultFormatter</code>,
0036         * using an instance of <code>java.text.Format</code> to handle the
0037         * conversion to a String, and the conversion from a String.
0038         * <p>
0039         * If <code>getAllowsInvalid()</code> is false, this will ask the
0040         * <code>Format</code> to format the current text on every edit.
0041         * <p>
0042         * You can specify a minimum and maximum value by way of the 
0043         * <code>setMinimum</code> and <code>setMaximum</code> methods. In order
0044         * for this to work the values returned from <code>stringToValue</code> must be
0045         * comparable to the min/max values by way of the <code>Comparable</code>
0046         * interface.
0047         * <p>
0048         * Be careful how you configure the <code>Format</code> and the
0049         * <code>InternationalFormatter</code>, as it is possible to create a
0050         * situation where certain values can not be input. Consider the date
0051         * format 'M/d/yy', an <code>InternationalFormatter</code> that is always
0052         * valid (<code>setAllowsInvalid(false)</code>), is in overwrite mode
0053         * (<code>setOverwriteMode(true)</code>) and the date 7/1/99. In this
0054         * case the user will not be able to enter a two digit month or day of
0055         * month. To avoid this, the format should be 'MM/dd/yy'.
0056         * <p>
0057         * If <code>InternationalFormatter</code> is configured to only allow valid
0058         * values (<code>setAllowsInvalid(false)</code>), every valid edit will result
0059         * in the text of the <code>JFormattedTextField</code> being completely reset
0060         * from the <code>Format</code>.
0061         * The cursor position will also be adjusted as literal characters are
0062         * added/removed from the resulting String.
0063         * <p>
0064         * <code>InternationalFormatter</code>'s behavior of
0065         * <code>stringToValue</code> is  slightly different than that of
0066         * <code>DefaultTextFormatter</code>, it does the following:
0067         * <ol>
0068         *   <li><code>parseObject</code> is invoked on the <code>Format</code>
0069         *       specified by <code>setFormat</code>
0070         *   <li>If a Class has been set for the values (<code>setValueClass</code>),
0071         *       supers implementation is invoked to convert the value returned
0072         *       from <code>parseObject</code> to the appropriate class.
0073         *   <li>If a <code>ParseException</code> has not been thrown, and the value
0074         *       is outside the min/max a <code>ParseException</code> is thrown.
0075         *   <li>The value is returned.
0076         * </ol>
0077         * <code>InternationalFormatter</code> implements <code>stringToValue</code>
0078         * in this manner so that you can specify an alternate Class than
0079         * <code>Format</code> may return.
0080         * <p>
0081         * <strong>Warning:</strong>
0082         * Serialized objects of this class will not be compatible with
0083         * future Swing releases. The current serialization support is
0084         * appropriate for short term storage or RMI between applications running
0085         * the same version of Swing.  As of 1.4, support for long term storage
0086         * of all JavaBeans<sup><font size="-2">TM</font></sup>
0087         * has been added to the <code>java.beans</code> package.
0088         * Please see {@link java.beans.XMLEncoder}.
0089         *
0090         * @see java.text.Format
0091         * @see java.lang.Comparable
0092         *
0093         * @version 1.7 04/09/01
0094         * @since 1.4
0095         */
0096        public class InternationalFormatter extends DefaultFormatter {
0097            /**
0098             * Used by <code>getFields</code>.
0099             */
0100            private static final Format.Field[] EMPTY_FIELD_ARRAY = new Format.Field[0];
0101
0102            /**
0103             * Object used to handle the conversion.
0104             */
0105            private Format format;
0106            /**
0107             * Can be used to impose a maximum value.
0108             */
0109            private Comparable max;
0110            /**
0111             * Can be used to impose a minimum value.
0112             */
0113            private Comparable min;
0114
0115            /**
0116             * <code>InternationalFormatter</code>'s behavior is dicatated by a
0117             * <code>AttributedCharacterIterator</code> that is obtained from
0118             * the <code>Format</code>. On every edit, assuming
0119             * allows invalid is false, the <code>Format</code> instance is invoked
0120             * with <code>formatToCharacterIterator</code>. A <code>BitSet</code> is
0121             * also kept upto date with the non-literal characters, that is
0122             * for every index in the <code>AttributedCharacterIterator</code> an
0123             * entry in the bit set is updated based on the return value from
0124             * <code>isLiteral(Map)</code>. <code>isLiteral(int)</code> then uses
0125             * this cached information.
0126             * <p>
0127             * If allowsInvalid is false, every edit results in resetting the complete
0128             * text of the JTextComponent.
0129             * <p>
0130             * InternationalFormatterFilter can also provide two actions suitable for
0131             * incrementing and decrementing. To enable this a subclass must
0132             * override <code>getSupportsIncrement</code> to return true, and
0133             * override <code>adjustValue</code> to handle the changing of the
0134             * value. If you want to support changing the value outside of
0135             * the valid FieldPositions, you will need to override
0136             * <code>canIncrement</code>.
0137             */
0138            /**
0139             * A bit is set for every index identified in the
0140             * AttributedCharacterIterator that is not considered decoration.
0141             * This should only be used if validMask is true.
0142             */
0143            private transient BitSet literalMask;
0144            /**
0145             * Used to iterate over characters.
0146             */
0147            private transient AttributedCharacterIterator iterator;
0148            /**
0149             * True if the Format was able to convert the value to a String and
0150             * back.
0151             */
0152            private transient boolean validMask;
0153            /**
0154             * Current value being displayed.
0155             */
0156            private transient String string;
0157            /**
0158             * If true, DocumentFilter methods are unconditionally allowed,
0159             * and no checking is done on their values. This is used when
0160             * incrementing/decrementing via the actions.
0161             */
0162            private transient boolean ignoreDocumentMutate;
0163
0164            /**
0165             * Creates an <code>InternationalFormatter</code> with no
0166             * <code>Format</code> specified.
0167             */
0168            public InternationalFormatter() {
0169                setOverwriteMode(false);
0170            }
0171
0172            /**
0173             * Creates an <code>InternationalFormatter</code> with the specified
0174             * <code>Format</code> instance.
0175             *
0176             * @param format Format instance used for converting from/to Strings
0177             */
0178            public InternationalFormatter(Format format) {
0179                this ();
0180                setFormat(format);
0181            }
0182
0183            /**
0184             * Sets the format that dictates the legal values that can be edited
0185             * and displayed.
0186             *
0187             * @param format <code>Format</code> instance used for converting
0188             * from/to Strings
0189             */
0190            public void setFormat(Format format) {
0191                this .format = format;
0192            }
0193
0194            /**
0195             * Returns the format that dictates the legal values that can be edited
0196             * and displayed.
0197             *
0198             * @return Format instance used for converting from/to Strings
0199             */
0200            public Format getFormat() {
0201                return format;
0202            }
0203
0204            /**
0205             * Sets the minimum permissible value. If the <code>valueClass</code> has
0206             * not been specified, and <code>minimum</code> is non null, the
0207             * <code>valueClass</code> will be set to that of the class of
0208             * <code>minimum</code>.
0209             *
0210             * @param minimum Minimum legal value that can be input
0211             * @see #setValueClass
0212             */
0213            public void setMinimum(Comparable minimum) {
0214                if (getValueClass() == null && minimum != null) {
0215                    setValueClass(minimum.getClass());
0216                }
0217                min = minimum;
0218            }
0219
0220            /**
0221             * Returns the minimum permissible value.
0222             *
0223             * @return Minimum legal value that can be input
0224             */
0225            public Comparable getMinimum() {
0226                return min;
0227            }
0228
0229            /**
0230             * Sets the maximum permissible value. If the <code>valueClass</code> has
0231             * not been specified, and <code>max</code> is non null, the
0232             * <code>valueClass</code> will be set to that of the class of
0233             * <code>max</code>.
0234             *
0235             * @param max Maximum legal value that can be input
0236             * @see #setValueClass
0237             */
0238            public void setMaximum(Comparable max) {
0239                if (getValueClass() == null && max != null) {
0240                    setValueClass(max.getClass());
0241                }
0242                this .max = max;
0243            }
0244
0245            /**
0246             * Returns the maximum permissible value.
0247             *
0248             * @return Maximum legal value that can be input
0249             */
0250            public Comparable getMaximum() {
0251                return max;
0252            }
0253
0254            /**
0255             * Installs the <code>DefaultFormatter</code> onto a particular
0256             * <code>JFormattedTextField</code>.
0257             * This will invoke <code>valueToString</code> to convert the
0258             * current value from the <code>JFormattedTextField</code> to
0259             * a String. This will then install the <code>Action</code>s from
0260             * <code>getActions</code>, the <code>DocumentFilter</code>
0261             * returned from <code>getDocumentFilter</code> and the
0262             * <code>NavigationFilter</code> returned from
0263             * <code>getNavigationFilter</code> onto the
0264             * <code>JFormattedTextField</code>.
0265             * <p>
0266             * Subclasses will typically only need to override this if they
0267             * wish to install additional listeners on the
0268             * <code>JFormattedTextField</code>.
0269             * <p>
0270             * If there is a <code>ParseException</code> in converting the
0271             * current value to a String, this will set the text to an empty
0272             * String, and mark the <code>JFormattedTextField</code> as being
0273             * in an invalid state.
0274             * <p>
0275             * While this is a public method, this is typically only useful
0276             * for subclassers of <code>JFormattedTextField</code>.
0277             * <code>JFormattedTextField</code> will invoke this method at
0278             * the appropriate times when the value changes, or its internal
0279             * state changes.
0280             *
0281             * @param ftf JFormattedTextField to format for, may be null indicating
0282             *            uninstall from current JFormattedTextField.
0283             */
0284            public void install(JFormattedTextField ftf) {
0285                super .install(ftf);
0286                updateMaskIfNecessary();
0287                // invoked again as the mask should now be valid.
0288                positionCursorAtInitialLocation();
0289            }
0290
0291            /**
0292             * Returns a String representation of the Object <code>value</code>.
0293             * This invokes <code>format</code> on the current <code>Format</code>.
0294             *
0295             * @throws ParseException if there is an error in the conversion
0296             * @param value Value to convert
0297             * @return String representation of value
0298             */
0299            public String valueToString(Object value) throws ParseException {
0300                if (value == null) {
0301                    return "";
0302                }
0303                Format f = getFormat();
0304
0305                if (f == null) {
0306                    return value.toString();
0307                }
0308                return f.format(value);
0309            }
0310
0311            /**
0312             * Returns the <code>Object</code> representation of the
0313             * <code>String</code> <code>text</code>.
0314             *
0315             * @param text <code>String</code> to convert
0316             * @return <code>Object</code> representation of text
0317             * @throws ParseException if there is an error in the conversion
0318             */
0319            public Object stringToValue(String text) throws ParseException {
0320                Object value = stringToValue(text, getFormat());
0321
0322                // Convert to the value class if the Value returned from the
0323                // Format does not match.
0324                if (value != null && getValueClass() != null
0325                        && !getValueClass().isInstance(value)) {
0326                    value = super .stringToValue(value.toString());
0327                }
0328                try {
0329                    if (!isValidValue(value, true)) {
0330                        throw new ParseException(
0331                                "Value not within min/max range", 0);
0332                    }
0333                } catch (ClassCastException cce) {
0334                    throw new ParseException(
0335                            "Class cast exception comparing values: " + cce, 0);
0336                }
0337                return value;
0338            }
0339
0340            /**
0341             * Returns the <code>Format.Field</code> constants associated with
0342             * the text at <code>offset</code>. If <code>offset</code> is not
0343             * a valid location into the current text, this will return an
0344             * empty array.
0345             *
0346             * @param offset offset into text to be examined
0347             * @return Format.Field constants associated with the text at the
0348             *         given position.
0349             */
0350            public Format.Field[] getFields(int offset) {
0351                if (getAllowsInvalid()) {
0352                    // This will work if the currently edited value is valid.
0353                    updateMask();
0354                }
0355
0356                Map attrs = getAttributes(offset);
0357
0358                if (attrs != null && attrs.size() > 0) {
0359                    ArrayList al = new ArrayList();
0360
0361                    al.addAll(attrs.keySet());
0362                    return (Format.Field[]) al.toArray(EMPTY_FIELD_ARRAY);
0363                }
0364                return EMPTY_FIELD_ARRAY;
0365            }
0366
0367            /**
0368             * Creates a copy of the DefaultFormatter.
0369             *
0370             * @return copy of the DefaultFormatter
0371             */
0372            public Object clone() throws CloneNotSupportedException {
0373                InternationalFormatter formatter = (InternationalFormatter) super 
0374                        .clone();
0375
0376                formatter.literalMask = null;
0377                formatter.iterator = null;
0378                formatter.validMask = false;
0379                formatter.string = null;
0380                return formatter;
0381            }
0382
0383            /**
0384             * If <code>getSupportsIncrement</code> returns true, this returns
0385             * two Actions suitable for incrementing/decrementing the value.
0386             */
0387            protected Action[] getActions() {
0388                if (getSupportsIncrement()) {
0389                    return new Action[] { new IncrementAction("increment", 1),
0390                            new IncrementAction("decrement", -1) };
0391                }
0392                return null;
0393            }
0394
0395            /**
0396             * Invokes <code>parseObject</code> on <code>f</code>, returning
0397             * its value.
0398             */
0399            Object stringToValue(String text, Format f) throws ParseException {
0400                if (f == null) {
0401                    return text;
0402                }
0403                return f.parseObject(text);
0404            }
0405
0406            /**
0407             * Returns true if <code>value</code> is between the min/max.
0408             *
0409             * @param wantsCCE If false, and a ClassCastException is thrown in
0410             *                 comparing the values, the exception is consumed and
0411             *                 false is returned.
0412             */
0413            boolean isValidValue(Object value, boolean wantsCCE) {
0414                Comparable min = getMinimum();
0415
0416                try {
0417                    if (min != null && min.compareTo(value) > 0) {
0418                        return false;
0419                    }
0420                } catch (ClassCastException cce) {
0421                    if (wantsCCE) {
0422                        throw cce;
0423                    }
0424                    return false;
0425                }
0426
0427                Comparable max = getMaximum();
0428                try {
0429                    if (max != null && max.compareTo(value) < 0) {
0430                        return false;
0431                    }
0432                } catch (ClassCastException cce) {
0433                    if (wantsCCE) {
0434                        throw cce;
0435                    }
0436                    return false;
0437                }
0438                return true;
0439            }
0440
0441            /**
0442             * Returns a Set of the attribute identifiers at <code>index</code>.
0443             */
0444            Map getAttributes(int index) {
0445                if (isValidMask()) {
0446                    AttributedCharacterIterator iterator = getIterator();
0447
0448                    if (index >= 0 && index <= iterator.getEndIndex()) {
0449                        iterator.setIndex(index);
0450                        return iterator.getAttributes();
0451                    }
0452                }
0453                return null;
0454            }
0455
0456            /**
0457             * Returns the start of the first run that contains the attribute
0458             * <code>id</code>. This will return <code>-1</code> if the attribute
0459             * can not be found.
0460             */
0461            int getAttributeStart(AttributedCharacterIterator.Attribute id) {
0462                if (isValidMask()) {
0463                    AttributedCharacterIterator iterator = getIterator();
0464
0465                    iterator.first();
0466                    while (iterator.current() != CharacterIterator.DONE) {
0467                        if (iterator.getAttribute(id) != null) {
0468                            return iterator.getIndex();
0469                        }
0470                        iterator.next();
0471                    }
0472                }
0473                return -1;
0474            }
0475
0476            /**
0477             * Returns the <code>AttributedCharacterIterator</code> used to
0478             * format the last value.
0479             */
0480            AttributedCharacterIterator getIterator() {
0481                return iterator;
0482            }
0483
0484            /**
0485             * Updates the AttributedCharacterIterator and bitset, if necessary.
0486             */
0487            void updateMaskIfNecessary() {
0488                if (!getAllowsInvalid() && (getFormat() != null)) {
0489                    if (!isValidMask()) {
0490                        updateMask();
0491                    } else {
0492                        String newString = getFormattedTextField().getText();
0493
0494                        if (!newString.equals(string)) {
0495                            updateMask();
0496                        }
0497                    }
0498                }
0499            }
0500
0501            /**
0502             * Updates the AttributedCharacterIterator by invoking
0503             * <code>formatToCharacterIterator</code> on the <code>Format</code>.
0504             * If this is successful,
0505             * <code>updateMask(AttributedCharacterIterator)</code>
0506             * is then invoked to update the internal bitmask.
0507             */
0508            void updateMask() {
0509                if (getFormat() != null) {
0510                    Document doc = getFormattedTextField().getDocument();
0511
0512                    validMask = false;
0513                    if (doc != null) {
0514                        try {
0515                            string = doc.getText(0, doc.getLength());
0516                        } catch (BadLocationException ble) {
0517                            string = null;
0518                        }
0519                        if (string != null) {
0520                            try {
0521                                Object value = stringToValue(string);
0522                                AttributedCharacterIterator iterator = getFormat()
0523                                        .formatToCharacterIterator(value);
0524
0525                                updateMask(iterator);
0526                            } catch (ParseException pe) {
0527                            } catch (IllegalArgumentException iae) {
0528                            } catch (NullPointerException npe) {
0529                            }
0530                        }
0531                    }
0532                }
0533            }
0534
0535            /**
0536             * Returns the number of literal characters before <code>index</code>.
0537             */
0538            int getLiteralCountTo(int index) {
0539                int lCount = 0;
0540
0541                for (int counter = 0; counter < index; counter++) {
0542                    if (isLiteral(counter)) {
0543                        lCount++;
0544                    }
0545                }
0546                return lCount;
0547            }
0548
0549            /**
0550             * Returns true if the character at index is a literal, that is
0551             * not editable.
0552             */
0553            boolean isLiteral(int index) {
0554                if (isValidMask() && index < string.length()) {
0555                    return literalMask.get(index);
0556                }
0557                return false;
0558            }
0559
0560            /**
0561             * Returns the literal character at index.
0562             */
0563            char getLiteral(int index) {
0564                if (isValidMask() && string != null && index < string.length()) {
0565                    return string.charAt(index);
0566                }
0567                return (char) 0;
0568            }
0569
0570            /**
0571             * Returns true if the character at offset is navigatable too. This
0572             * is implemented in terms of <code>isLiteral</code>, subclasses
0573             * may wish to provide different behavior.
0574             */
0575            boolean isNavigatable(int offset) {
0576                return !isLiteral(offset);
0577            }
0578
0579            /**
0580             * Overriden to update the mask after invoking supers implementation.
0581             */
0582            void updateValue(Object value) {
0583                super .updateValue(value);
0584                updateMaskIfNecessary();
0585            }
0586
0587            /**
0588             * Overriden to unconditionally allow the replace if
0589             * ignoreDocumentMutate is true.
0590             */
0591            void replace(DocumentFilter.FilterBypass fb, int offset,
0592                    int length, String text, AttributeSet attrs)
0593                    throws BadLocationException {
0594                if (ignoreDocumentMutate) {
0595                    fb.replace(offset, length, text, attrs);
0596                    return;
0597                }
0598                super .replace(fb, offset, length, text, attrs);
0599            }
0600
0601            /**
0602             * Returns the index of the next non-literal character starting at
0603             * index. If index is not a literal, it will be returned.
0604             *
0605             * @param direction Amount to increment looking for non-literal
0606             */
0607            private int getNextNonliteralIndex(int index, int direction) {
0608                int max = getFormattedTextField().getDocument().getLength();
0609
0610                while (index >= 0 && index < max) {
0611                    if (isLiteral(index)) {
0612                        index += direction;
0613                    } else {
0614                        return index;
0615                    }
0616                }
0617                return (direction == -1) ? 0 : max;
0618            }
0619
0620            /**
0621             * Overriden in an attempt to honor the literals.
0622             * <p>
0623             * If we do
0624             * not allow invalid values and are in overwrite mode, this does the
0625             * following for each character in the replacement range:
0626             * <ol>
0627             *   <li>If the character is a literal, add it to the string to replace
0628             *       with.  If there is text to insert and it doesn't match the
0629             *       literal, then insert the literal in the the middle of the insert
0630             *       text.  This allows you to either paste in literals or not and
0631             *       get the same behavior.
0632             *   <li>If there is no text to insert, replace it with ' '.
0633             * </ol>
0634             * If not in overwrite mode, and there is text to insert it is
0635             * inserted at the next non literal index going forward.  If there
0636             * is only text to remove, it is removed from the next non literal
0637             * index going backward.
0638             */
0639            boolean canReplace(ReplaceHolder rh) {
0640                if (!getAllowsInvalid()) {
0641                    String text = rh.text;
0642                    int tl = (text != null) ? text.length() : 0;
0643
0644                    if (tl == 0
0645                            && rh.length == 1
0646                            && getFormattedTextField().getSelectionStart() != rh.offset) {
0647                        // Backspace, adjust to actually delete next non-literal.
0648                        rh.offset = getNextNonliteralIndex(rh.offset, -1);
0649                    }
0650                    if (getOverwriteMode()) {
0651                        StringBuffer replace = null;
0652
0653                        for (int counter = 0, textIndex = 0, max = Math.max(tl,
0654                                rh.length); counter < max; counter++) {
0655                            if (isLiteral(rh.offset + counter)) {
0656                                if (replace != null) {
0657                                    replace.append(getLiteral(rh.offset
0658                                            + counter));
0659                                }
0660                                if (textIndex < tl
0661                                        && text.charAt(textIndex) == getLiteral(rh.offset
0662                                                + counter)) {
0663                                    textIndex++;
0664                                } else if (textIndex == 0) {
0665                                    rh.offset++;
0666                                    rh.length--;
0667                                    counter--;
0668                                    max--;
0669                                } else if (replace == null) {
0670                                    replace = new StringBuffer(max);
0671                                    replace
0672                                            .append(text
0673                                                    .substring(0, textIndex));
0674                                    replace.append(getLiteral(rh.offset
0675                                            + counter));
0676                                }
0677                            } else if (textIndex < tl) {
0678                                if (replace != null) {
0679                                    replace.append(text.charAt(textIndex));
0680                                }
0681                                textIndex++;
0682                            } else {
0683                                // Nothing to replace it with, assume ' '
0684                                if (replace == null) {
0685                                    replace = new StringBuffer(max);
0686                                    if (textIndex > 0) {
0687                                        replace.append(text.substring(0,
0688                                                textIndex));
0689                                    }
0690                                }
0691                                if (replace != null) {
0692                                    replace.append(' ');
0693                                }
0694                            }
0695                        }
0696                        if (replace != null) {
0697                            rh.text = replace.toString();
0698                        }
0699                    } else if (tl > 0) {
0700                        // insert (or insert and remove)
0701                        rh.offset = getNextNonliteralIndex(rh.offset, 1);
0702                    } else {
0703                        // remove only
0704                        rh.offset = getNextNonliteralIndex(rh.offset, -1);
0705                    }
0706                    ((ExtendedReplaceHolder) rh).endOffset = rh.offset;
0707                    ((ExtendedReplaceHolder) rh).endTextLength = (rh.text != null) ? rh.text
0708                            .length()
0709                            : 0;
0710                } else {
0711                    ((ExtendedReplaceHolder) rh).endOffset = rh.offset;
0712                    ((ExtendedReplaceHolder) rh).endTextLength = (rh.text != null) ? rh.text
0713                            .length()
0714                            : 0;
0715                }
0716                boolean can = super .canReplace(rh);
0717                if (can && !getAllowsInvalid()) {
0718                    ((ExtendedReplaceHolder) rh).resetFromValue(this );
0719                }
0720                return can;
0721            }
0722
0723            /**
0724             * When in !allowsInvalid mode the text is reset on every edit, thus
0725             * supers implementation will position the cursor at the wrong position.
0726             * As such, this invokes supers implementation and then invokes
0727             * <code>repositionCursor</code> to correctly reset the cursor.
0728             */
0729            boolean replace(ReplaceHolder rh) throws BadLocationException {
0730                int start = -1;
0731                int direction = 1;
0732                int literalCount = -1;
0733
0734                if (rh.length > 0
0735                        && (rh.text == null || rh.text.length() == 0)
0736                        && (getFormattedTextField().getSelectionStart() != rh.offset || rh.length > 1)) {
0737                    direction = -1;
0738                }
0739                if (!getAllowsInvalid()) {
0740                    if ((rh.text == null || rh.text.length() == 0)
0741                            && rh.length > 0) {
0742                        // remove
0743                        start = getFormattedTextField().getSelectionStart();
0744                    } else {
0745                        start = rh.offset;
0746                    }
0747                    literalCount = getLiteralCountTo(start);
0748                }
0749                if (super .replace(rh)) {
0750                    if (start != -1) {
0751                        int end = ((ExtendedReplaceHolder) rh).endOffset;
0752
0753                        end += ((ExtendedReplaceHolder) rh).endTextLength;
0754                        repositionCursor(literalCount, end, direction);
0755                    } else {
0756                        start = ((ExtendedReplaceHolder) rh).endOffset;
0757                        if (direction == 1) {
0758                            start += ((ExtendedReplaceHolder) rh).endTextLength;
0759                        }
0760                        repositionCursor(start, direction);
0761                    }
0762                    return true;
0763                }
0764                return false;
0765            }
0766
0767            /**
0768             * Repositions the cursor. <code>startLiteralCount</code> gives
0769             * the number of literals to the start of the deleted range, end
0770             * gives the ending location to adjust from, direction gives
0771             * the direction relative to <code>end</code> to position the
0772             * cursor from.
0773             */
0774            private void repositionCursor(int startLiteralCount, int end,
0775                    int direction) {
0776                int endLiteralCount = getLiteralCountTo(end);
0777
0778                if (endLiteralCount != end) {
0779                    end -= startLiteralCount;
0780                    for (int counter = 0; counter < end; counter++) {
0781                        if (isLiteral(counter)) {
0782                            end++;
0783                        }
0784                    }
0785                }
0786                repositionCursor(end, 1 /*direction*/);
0787            }
0788
0789            /**
0790             * Returns the character from the mask that has been buffered
0791             * at <code>index</code>.
0792             */
0793            char getBufferedChar(int index) {
0794                if (isValidMask()) {
0795                    if (string != null && index < string.length()) {
0796                        return string.charAt(index);
0797                    }
0798                }
0799                return (char) 0;
0800            }
0801
0802            /**
0803             * Returns true if the current mask is valid.
0804             */
0805            boolean isValidMask() {
0806                return validMask;
0807            }
0808
0809            /**
0810             * Returns true if <code>attributes</code> is null or empty.
0811             */
0812            boolean isLiteral(Map attributes) {
0813                return ((attributes == null) || attributes.size() == 0);
0814            }
0815
0816            /**
0817             * Updates the interal bitset from <code>iterator</code>. This will
0818             * set <code>validMask</code> to true if <code>iterator</code> is
0819             * non-null.
0820             */
0821            private void updateMask(AttributedCharacterIterator iterator) {
0822                if (iterator != null) {
0823                    validMask = true;
0824                    this .iterator = iterator;
0825
0826                    // Update the literal mask
0827                    if (literalMask == null) {
0828                        literalMask = new BitSet();
0829                    } else {
0830                        for (int counter = literalMask.length() - 1; counter >= 0; counter--) {
0831                            literalMask.clear(counter);
0832                        }
0833                    }
0834
0835                    iterator.first();
0836                    while (iterator.current() != CharacterIterator.DONE) {
0837                        Map attributes = iterator.getAttributes();
0838                        boolean set = isLiteral(attributes);
0839                        int start = iterator.getIndex();
0840                        int end = iterator.getRunLimit();
0841
0842                        while (start < end) {
0843                            if (set) {
0844                                literalMask.set(start);
0845                            } else {
0846                                literalMask.clear(start);
0847                            }
0848                            start++;
0849                        }
0850                        iterator.setIndex(start);
0851                    }
0852                }
0853            }
0854
0855            /**
0856             * Returns true if <code>field</code> is non-null.
0857             * Subclasses that wish to allow incrementing to happen outside of
0858             * the known fields will need to override this.
0859             */
0860            boolean canIncrement(Object field, int cursorPosition) {
0861                return (field != null);
0862            }
0863
0864            /**
0865             * Selects the fields identified by <code>attributes</code>.
0866             */
0867            void selectField(Object f, int count) {
0868                AttributedCharacterIterator iterator = getIterator();
0869
0870                if (iterator != null
0871                        && (f instanceof  AttributedCharacterIterator.Attribute)) {
0872                    AttributedCharacterIterator.Attribute field = (AttributedCharacterIterator.Attribute) f;
0873
0874                    iterator.first();
0875                    while (iterator.current() != CharacterIterator.DONE) {
0876                        while (iterator.getAttribute(field) == null
0877                                && iterator.next() != CharacterIterator.DONE)
0878                            ;
0879                        if (iterator.current() != CharacterIterator.DONE) {
0880                            int limit = iterator.getRunLimit(field);
0881
0882                            if (--count <= 0) {
0883                                getFormattedTextField().select(
0884                                        iterator.getIndex(), limit);
0885                                break;
0886                            }
0887                            iterator.setIndex(limit);
0888                            iterator.next();
0889                        }
0890                    }
0891                }
0892            }
0893
0894            /**
0895             * Returns the field that will be adjusted by adjustValue.
0896             */
0897            Object getAdjustField(int start, Map attributes) {
0898                return null;
0899            }
0900
0901            /**
0902             * Returns the number of occurences of <code>f</code> before
0903             * the location <code>start</code> in the current
0904             * <code>AttributedCharacterIterator</code>.
0905             */
0906            private int getFieldTypeCountTo(Object f, int start) {
0907                AttributedCharacterIterator iterator = getIterator();
0908                int count = 0;
0909
0910                if (iterator != null
0911                        && (f instanceof  AttributedCharacterIterator.Attribute)) {
0912                    AttributedCharacterIterator.Attribute field = (AttributedCharacterIterator.Attribute) f;
0913                    int index = 0;
0914
0915                    iterator.first();
0916                    while (iterator.getIndex() < start) {
0917                        while (iterator.getAttribute(field) == null
0918                                && iterator.next() != CharacterIterator.DONE)
0919                            ;
0920                        if (iterator.current() != CharacterIterator.DONE) {
0921                            iterator.setIndex(iterator.getRunLimit(field));
0922                            iterator.next();
0923                            count++;
0924                        } else {
0925                            break;
0926                        }
0927                    }
0928                }
0929                return count;
0930            }
0931
0932            /**
0933             * Subclasses supporting incrementing must override this to handle
0934             * the actual incrementing. <code>value</code> is the current value,
0935             * <code>attributes</code> gives the field the cursor is in (may be
0936             * null depending upon <code>canIncrement</code>) and 
0937             * <code>direction</code> is the amount to increment by.
0938             */
0939            Object adjustValue(Object value, Map attributes, Object field,
0940                    int direction) throws BadLocationException, ParseException {
0941                return null;
0942            }
0943
0944            /**
0945             * Returns false, indicating InternationalFormatter does not allow
0946             * incrementing of the value. Subclasses that wish to support 
0947             * incrementing/decrementing the value should override this and
0948             * return true. Subclasses should also override
0949             * <code>adjustValue</code>.
0950             */
0951            boolean getSupportsIncrement() {
0952                return false;
0953            }
0954
0955            /**
0956             * Resets the value of the JFormattedTextField to be
0957             * <code>value</code>.
0958             */
0959            void resetValue(Object value) throws BadLocationException,
0960                    ParseException {
0961                Document doc = getFormattedTextField().getDocument();
0962                String string = valueToString(value);
0963
0964                try {
0965                    ignoreDocumentMutate = true;
0966                    doc.remove(0, doc.getLength());
0967                    doc.insertString(0, string, null);
0968                } finally {
0969                    ignoreDocumentMutate = false;
0970                }
0971                updateValue(value);
0972            }
0973
0974            /**
0975             * Subclassed to update the internal representation of the mask after
0976             * the default read operation has completed.
0977             */
0978            private void readObject(ObjectInputStream s) throws IOException,
0979                    ClassNotFoundException {
0980                s.defaultReadObject();
0981                updateMaskIfNecessary();
0982            }
0983
0984            /**
0985             * Overriden to return an instance of <code>ExtendedReplaceHolder</code>.
0986             */
0987            ReplaceHolder getReplaceHolder(DocumentFilter.FilterBypass fb,
0988                    int offset, int length, String text, AttributeSet attrs) {
0989                if (replaceHolder == null) {
0990                    replaceHolder = new ExtendedReplaceHolder();
0991                }
0992                return super .getReplaceHolder(fb, offset, length, text, attrs);
0993            }
0994
0995            /**
0996             * As InternationalFormatter replaces the complete text on every edit,
0997             * ExtendedReplaceHolder keeps track of the offset and length passed
0998             * into canReplace.
0999             */
1000            static class ExtendedReplaceHolder extends ReplaceHolder {
1001                /** Offset of the insert/remove. This may differ from offset in
1002                 * that if !allowsInvalid the text is replaced on every edit. */
1003                int endOffset;
1004                /** Length of the text. This may differ from text.length in
1005                 * that if !allowsInvalid the text is replaced on every edit. */
1006                int endTextLength;
1007
1008                /**
1009                 * Resets the region to delete to be the complete document and
1010                 * the text from invoking valueToString on the current value.
1011                 */
1012                void resetFromValue(InternationalFormatter formatter) {
1013                    // Need to reset the complete string as Format's result can
1014                    // be completely different.
1015                    offset = 0;
1016                    try {
1017                        text = formatter.valueToString(value);
1018                    } catch (ParseException pe) {
1019                        // Should never happen, otherwise canReplace would have
1020                        // returned value.
1021                        text = "";
1022                    }
1023                    length = fb.getDocument().getLength();
1024                }
1025            }
1026
1027            /**
1028             * IncrementAction is used to increment the value by a certain amount.
1029             * It calls into <code>adjustValue</code> to handle the actual
1030             * incrementing of the value.
1031             */
1032            private class IncrementAction extends AbstractAction {
1033                private int direction;
1034
1035                IncrementAction(String name, int direction) {
1036                    super (name);
1037                    this .direction = direction;
1038                }
1039
1040                public void actionPerformed(ActionEvent ae) {
1041
1042                    if (getFormattedTextField().isEditable()) {
1043                        if (getAllowsInvalid()) {
1044                            // This will work if the currently edited value is valid.
1045                            updateMask();
1046                        }
1047
1048                        boolean validEdit = false;
1049
1050                        if (isValidMask()) {
1051                            int start = getFormattedTextField()
1052                                    .getSelectionStart();
1053
1054                            if (start != -1) {
1055                                AttributedCharacterIterator iterator = getIterator();
1056
1057                                iterator.setIndex(start);
1058
1059                                Map attributes = iterator.getAttributes();
1060                                Object field = getAdjustField(start, attributes);
1061
1062                                if (canIncrement(field, start)) {
1063                                    try {
1064                                        Object value = stringToValue(getFormattedTextField()
1065                                                .getText());
1066                                        int fieldTypeCount = getFieldTypeCountTo(
1067                                                field, start);
1068
1069                                        value = adjustValue(value, attributes,
1070                                                field, direction);
1071                                        if (value != null
1072                                                && isValidValue(value, false)) {
1073                                            resetValue(value);
1074                                            updateMask();
1075
1076                                            if (isValidMask()) {
1077                                                selectField(field,
1078                                                        fieldTypeCount);
1079                                            }
1080                                            validEdit = true;
1081                                        }
1082                                    } catch (ParseException pe) {
1083                                    } catch (BadLocationException ble) {
1084                                    }
1085                                }
1086                            }
1087                        }
1088                        if (!validEdit) {
1089                            invalidEdit();
1090                        }
1091                    }
1092                }
1093            }
1094        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.