001/**
002 *   Please feel free to use any fragment of the code in this file that you need
003 *   in your own work. As far as I am concerned, it's in the public domain. No
004 *   permission is necessary or required. Credit is always appreciated if you
005 *   use a large chunk or base a significant product on one of my examples,
006 *   but that's not required either.
007 *
008 *   This code is distributed in the hope that it will be useful,
009 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
010 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
011 *
012 *      --- Joseph A. Huwaldt
013 */
014package jahuwaldt.swing;
015
016import java.awt.Toolkit;
017import java.text.DecimalFormat;
018import java.text.DecimalFormatSymbols;
019import java.text.Format;
020import java.text.ParsePosition;
021import javax.swing.text.AttributeSet;
022import javax.swing.text.BadLocationException;
023import javax.swing.text.PlainDocument;
024
025/**
026 * A formatted document that uses a user supplied Format object to control the format of
027 * the text in the document. For example, this document will ignore anything that isn't a
028 * number if a NumberFormat is supplied.
029 *
030 * <p> Modified by: Joseph A. Huwaldt </p>
031 *
032 * @author Joseph A. Huwaldt, Date: February 24, 2000
033 * @version August 3, 2018
034 */
035@SuppressWarnings("serial")
036public class FormattedDocument extends PlainDocument {
037
038    private final Format format;
039    private final ParsePosition pos = new ParsePosition(0);
040    private String decimalSymbol = null;
041    private String minusSignSymbol = null;
042
043    /**
044     * Construct a formatted document that uses the supplied Format object.
045     *
046     * @param f The format to use for this document.
047     */
048    public FormattedDocument(Format f) {
049        format = f;
050        if (format instanceof DecimalFormat) {
051            DecimalFormatSymbols symbols = ((DecimalFormat)format).getDecimalFormatSymbols();
052            decimalSymbol = String.valueOf(symbols.getDecimalSeparator());
053            minusSignSymbol = String.valueOf(symbols.getMinusSign());
054        }
055    }
056
057    /**
058     * Method that returns the Format used by this document.
059     *
060     * @return The format used by this document.
061     */
062    public Format getFormat() {
063        return format;
064    }
065
066    /**
067     * Inserts some content into the document using the documents Format to validate what
068     * has be inserted. Inserting content causes a write lock to be held while the actual
069     * changes are taking place, followed by notification to the observers on the thread
070     * that grabbed the write lock.
071     *
072     * @param offs the starting offset >= 0
073     * @param str  the string to insert; does nothing with null/empty strings
074     * @param a    the attributes for the inserted content
075     * @throws javax.swing.text.BadLocationException
076     */
077    @Override
078    public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
079
080        if (str == null || str.length() == 0)
081            return;
082
083        String currentText = getText(0, getLength());
084        String beforeOffset = currentText.substring(0, offs);
085        String afterOffset = currentText.substring(offs, currentText.length());
086        String proposedResult = beforeOffset + str + afterOffset;
087
088        //  Work around a couple of "bugs" in DecimalFormat.parseObject().
089        //  Allow leading decimal places when typing in a number.
090        if (format instanceof DecimalFormat
091                && (proposedResult.equals(decimalSymbol) || proposedResult.equals(minusSignSymbol + decimalSymbol)))
092            super.insertString(offs, str, a);
093
094        else {
095            pos.setIndex(0);
096            format.parseObject(proposedResult, pos);
097            if (pos.getIndex() == proposedResult.length())
098                super.insertString(offs, str, a);
099
100            else {
101                System.err.println("FormattedDocument.insertString() error.");
102                Toolkit.getDefaultToolkit().beep();
103            }
104        }
105    }
106
107    /**
108     * Removes some content from the document. Removing content causes a write lock to be
109     * held while the actual changes are taking place. Observers are notified of the
110     * change on the thread that called this method.
111     *
112     * @param offs the starting offset >= 0
113     * @param len  the number of characters to remove >= 0
114     * @throws javax.swing.text.BadLocationException
115     */
116    @Override
117    public void remove(int offs, int len) throws BadLocationException {
118        String currentText = getText(0, getLength());
119        String beforeOffset = currentText.substring(0, offs);
120        String afterOffset = currentText.substring(len + offs, currentText.length());
121        String proposedResult = beforeOffset + afterOffset;
122
123        if (proposedResult.length() != 0) {
124            pos.setIndex(0);
125            format.parseObject(proposedResult, pos);
126            if (pos.getIndex() != proposedResult.length()) {
127                System.out.println("FormattedDocument.remove() error.");
128                Toolkit.getDefaultToolkit().beep();
129                return;
130            }
131        }
132
133        super.remove(offs, len);
134    }
135}