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}