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 February 23, 2025 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 the given insert position is not 076 * a valid position within the document. 077 */ 078 @Override 079 public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { 080 081 if (str == null || str.length() == 0) 082 return; 083 084 String currentText = getText(0, getLength()); 085 String beforeOffset = currentText.substring(0, offs); 086 String afterOffset = currentText.substring(offs, currentText.length()); 087 String proposedResult = beforeOffset + str + afterOffset; 088 089 // Work around a couple of "bugs" in DecimalFormat.parseObject(). 090 // Allow leading decimal places when typing in a number. 091 if (format instanceof DecimalFormat 092 && (proposedResult.equals(decimalSymbol) || proposedResult.equals(minusSignSymbol + decimalSymbol))) 093 super.insertString(offs, str, a); 094 095 else { 096 pos.setIndex(0); 097 format.parseObject(proposedResult, pos); 098 if (pos.getIndex() == proposedResult.length()) 099 super.insertString(offs, str, a); 100 101 else { 102 System.err.println("FormattedDocument.insertString() error."); 103 Toolkit.getDefaultToolkit().beep(); 104 } 105 } 106 } 107 108 /** 109 * Removes some content from the document. Removing content causes a write lock to be 110 * held while the actual changes are taking place. Observers are notified of the 111 * change on the thread that called this method. 112 * 113 * @param offs the starting offset ≥ 0 114 * @param len the number of characters to remove ≥ 0 115 * @throws javax.swing.text.BadLocationException the given remove position is 116 * not a valid position within the document 117 */ 118 @Override 119 public void remove(int offs, int len) throws BadLocationException { 120 String currentText = getText(0, getLength()); 121 String beforeOffset = currentText.substring(0, offs); 122 String afterOffset = currentText.substring(len + offs, currentText.length()); 123 String proposedResult = beforeOffset + afterOffset; 124 125 if (proposedResult.length() != 0) { 126 pos.setIndex(0); 127 format.parseObject(proposedResult, pos); 128 if (pos.getIndex() != proposedResult.length()) { 129 System.out.println("FormattedDocument.remove() error."); 130 Toolkit.getDefaultToolkit().beep(); 131 return; 132 } 133 } 134 135 super.remove(offs, len); 136 } 137}