001: /*
002: * Gruntspud
003: *
004: * Copyright (C) 2002 Brett Smith.
005: *
006: * Written by: Brett Smith <t_magicthize@users.sourceforge.net>
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Library General Public License
010: * as published by the Free Software Foundation; either version 2 of
011: * the License, or (at your option) any later version.
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU Library General Public License for more details.
016: *
017: * You should have received a copy of the GNU Library General Public
018: * License along with this program; if not, write to the Free Software
019: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
020: */
021:
022: package gruntspud.ui;
023:
024: import java.awt.Color;
025: import java.awt.FontMetrics;
026: import java.awt.event.FocusEvent;
027: import java.text.DecimalFormat;
028: import java.text.DecimalFormatSymbols;
029: import java.text.NumberFormat;
030: import java.text.ParseException;
031:
032: import javax.swing.JTextField;
033: import javax.swing.text.AttributeSet;
034: import javax.swing.text.BadLocationException;
035: import javax.swing.text.PlainDocument;
036:
037: /**
038: * DOCUMENT ME!
039: *
040: * @author $author$
041: */
042: public class JNumericTextField extends XTextField {
043: // Private instance variables
044: private NumberFormat numberFormat;
045: private boolean selectAllOnFocusGain;
046: private Color positiveBackground;
047: private DecimalFormatSymbols symbols;
048: private int wColumnWidth;
049:
050: /**
051: * Creates a new JNumericTextField object.
052: *
053: * @param min DOCUMENT ME!
054: * @param max DOCUMENT ME!
055: * @param initial DOCUMENT ME!
056: */
057: public JNumericTextField(Number min, Number max, Number initial) {
058: this (min, max, initial, true);
059: }
060:
061: /**
062: * Creates a new JNumericTextField object.
063: *
064: * @param min DOCUMENT ME!
065: * @param max DOCUMENT ME!
066: * @param initial DOCUMENT ME!
067: * @param rightJustify DOCUMENT ME!
068: * @param numberFormat DOCUMENT ME!
069: */
070: public JNumericTextField(Number min, Number max, Number initial,
071: boolean rightJustify, NumberFormat numberFormat) {
072: super (Math
073: .max(min.toString().length(), max.toString().length()));
074: setNumberFormat(numberFormat);
075: setSelectAllOnFocusGain(true);
076:
077: if (min.getClass().equals(max.getClass())
078: && max.getClass().equals(initial.getClass())) {
079: setDocument(new ADocument(min, max));
080: setValue(initial);
081: } else {
082: throw new IllegalArgumentException(
083: "All arguments must be of the same class");
084: }
085:
086: setRightJustify(rightJustify);
087: }
088:
089: /**
090: * Creates a new JNumericTextField object.
091: *
092: * @param min DOCUMENT ME!
093: * @param max DOCUMENT ME!
094: */
095: public JNumericTextField(Number min, Number max) {
096: this (min, max, min);
097: }
098:
099: /**
100: * Creates a new JNumericTextField object.
101: *
102: * @param min DOCUMENT ME!
103: * @param max DOCUMENT ME!
104: * @param initial DOCUMENT ME!
105: * @param rightJustify DOCUMENT ME!
106: */
107: public JNumericTextField(Number min, Number max, Number initial,
108: boolean rightJustify) {
109: this (min, max, initial, rightJustify, null);
110: }
111:
112: /**
113: * DOCUMENT ME!
114: *
115: * @return DOCUMENT ME!
116: */
117: public Number getMinimum() {
118: return ((ADocument) getDocument()).min;
119: }
120:
121: /**
122: * DOCUMENT ME!
123: *
124: * @return DOCUMENT ME!
125: */
126: public Number getValue() {
127: return ((ADocument) getDocument()).getValue();
128: }
129:
130: /**
131: * DOCUMENT ME!
132: *
133: * @param min DOCUMENT ME!
134: */
135: public void setMinimum(Number min) {
136: ((ADocument) getDocument()).setMinimum(min);
137: }
138:
139: /**
140: * DOCUMENT ME!
141: *
142: * @param numberFormat DOCUMENT ME!
143: */
144: public void setNumberFormat(NumberFormat numberFormat) {
145: this .numberFormat = numberFormat;
146:
147: if (numberFormat instanceof DecimalFormat) {
148: symbols = ((DecimalFormat) numberFormat)
149: .getDecimalFormatSymbols();
150: } else {
151: symbols = new DecimalFormatSymbols();
152: }
153: }
154:
155: /**
156: * DOCUMENT ME!
157: *
158: * @param i DOCUMENT ME!
159: */
160: public void setValue(Number i) {
161: setText(i.toString());
162: }
163:
164: /**
165: * DOCUMENT ME!
166: *
167: * @return DOCUMENT ME!
168: */
169: public boolean isRightJustify() {
170: return getHorizontalAlignment() == JTextField.RIGHT;
171: }
172:
173: /**
174: * DOCUMENT ME!
175: *
176: * @param selectAllOnFocusGain DOCUMENT ME!
177: */
178: public void setSelectAllOnFocusGain(boolean selectAllOnFocusGain) {
179: this .selectAllOnFocusGain = selectAllOnFocusGain;
180: }
181:
182: /**
183: * DOCUMENT ME!
184: *
185: * @return DOCUMENT ME!
186: */
187: public Number getMaximum() {
188: return ((ADocument) getDocument()).max;
189: }
190:
191: /**
192: * DOCUMENT ME!
193: *
194: * @return DOCUMENT ME!
195: */
196: public int getColumnWidth() {
197: if (wColumnWidth == 0) {
198: FontMetrics metrics = getFontMetrics(getFont());
199: wColumnWidth = metrics.charWidth('9');
200: }
201:
202: return wColumnWidth;
203: }
204:
205: /**
206: * DOCUMENT ME!
207: *
208: * @param rightJustify DOCUMENT ME!
209: */
210: public void setRightJustify(boolean rightJustify) {
211: setHorizontalAlignment(rightJustify ? JTextField.RIGHT
212: : JTextField.LEFT);
213: }
214:
215: /**
216: * DOCUMENT ME!
217: *
218: * @param s DOCUMENT ME!
219: */
220: public void setText(String s) {
221: ADocument doc = (ADocument) getDocument();
222: Number oldValue = doc.currentVal;
223:
224: try {
225: doc.currentVal = doc.parse(s);
226: } catch (Exception e) {
227: e.printStackTrace();
228:
229: return;
230: }
231:
232: if (oldValue != doc.currentVal) {
233: doc.checkingEnabled = false;
234: super .setText(s);
235: doc.checkingEnabled = true;
236: }
237: }
238:
239: /**
240: * DOCUMENT ME!
241: *
242: * @param max DOCUMENT ME!
243: */
244: public void setMaximum(Number max) {
245: ((ADocument) getDocument()).setMaximum(max);
246: }
247:
248: /**
249: * DOCUMENT ME!
250: *
251: * @return DOCUMENT ME!
252: */
253: public boolean isSelectAllOnFocusGain() {
254: return selectAllOnFocusGain;
255: }
256:
257: /**
258: * DOCUMENT ME!
259: *
260: * @return DOCUMENT ME!
261: */
262: public NumberFormat getNumberFormat() {
263: return numberFormat;
264: }
265:
266: protected void processFocusEvent(FocusEvent e) {
267: super .processFocusEvent(e);
268:
269: if (!e.isTemporary()) {
270: switch (e.getID()) {
271: case FocusEvent.FOCUS_LOST:
272:
273: if (getNumberFormat() != null) {
274: String s = getNumberFormat().format(getValue())
275: .toString();
276:
277: if (!getText().equals(s)) {
278: setText(s);
279: }
280: }
281:
282: break;
283: case FocusEvent.FOCUS_GAINED:
284:
285: if (isSelectAllOnFocusGain()) {
286: selectAll();
287:
288: }
289: break;
290: }
291: }
292: }
293:
294: // Supporting classes
295: class ADocument extends PlainDocument {
296: Number currentVal;
297: Number max;
298: Number min;
299: boolean rightJustify = true;
300: boolean checkingEnabled = true;
301:
302: public ADocument(Number min, Number max) {
303: this .min = min;
304: this .max = max;
305:
306: if (min.getClass().equals(Byte.class)) {
307: currentVal = new Byte((byte) 0);
308: } else {
309: if (min.getClass().equals(Short.class)) {
310: currentVal = new Short((short) 0);
311: } else {
312: if (min.getClass().equals(Integer.class)) {
313: currentVal = new Integer(0);
314: } else {
315: if (min.getClass().equals(Long.class)) {
316: currentVal = new Long(0L);
317: } else {
318: if (min.getClass().equals(Float.class)) {
319: currentVal = new Float(0f);
320: } else {
321: currentVal = new Double(0d);
322: }
323: }
324: }
325: }
326: }
327: }
328:
329: public void insertString(int offs, String str, AttributeSet a)
330: throws BadLocationException {
331: if (str == null) {
332: return;
333: }
334:
335: if (!checkingEnabled) {
336: super .insertString(offs, str, a);
337:
338: return;
339: }
340:
341: String proposedResult = null;
342:
343: if (getLength() == 0) {
344: proposedResult = str;
345: } else {
346: StringBuffer currentBuffer = new StringBuffer(getText(
347: 0, getLength()));
348: currentBuffer.insert(offs, str);
349: proposedResult = currentBuffer.toString();
350: }
351:
352: try {
353: currentVal = parse(proposedResult);
354: super .insertString(offs, str, a);
355: } catch (Exception e) {
356: }
357: }
358:
359: public boolean isRightJustify() {
360: return rightJustify;
361: }
362:
363: public Number getValue() {
364: return currentVal;
365: }
366:
367: public void setRightJustify(boolean rightJustify) {
368: this .rightJustify = rightJustify;
369: }
370:
371: public Number parse(String proposedResult)
372: throws NumberFormatException {
373: Double d = new Double(0d);
374:
375: // See if the proposed result matches the number format (if any)
376: if (!proposedResult.equals(String.valueOf(symbols
377: .getMinusSign()))
378: && (proposedResult.length() != 0)) {
379: if (getNumberFormat() != null) {
380: // Strip out everything from the proposed result other than the
381: // numbers and and decimal separators
382: StringBuffer sB = new StringBuffer();
383:
384: for (int i = 0; i < proposedResult.length(); i++) {
385: char ch = proposedResult.charAt(i);
386:
387: if ((ch == symbols.getDecimalSeparator())
388: || ((ch >= '0') && (ch <= '9'))) {
389: sB.append(ch);
390: }
391: }
392:
393: String s = sB.toString();
394:
395: // Find out how many digits there are before the decimal place
396: int i = 0;
397:
398: for (; (i < s.length())
399: && (s.charAt(i) != symbols
400: .getDecimalSeparator()); i++) {
401: ;
402: }
403:
404: int before = i;
405: int after = 0;
406:
407: if (before < s.length()) {
408: after = s.length() - i - 1;
409:
410: }
411: if (before > getNumberFormat()
412: .getMaximumIntegerDigits()) {
413: throw new NumberFormatException(
414: "More digits BEFORE the decimal separator than allowed:"
415: + proposedResult);
416: }
417:
418: if (after > getNumberFormat()
419: .getMaximumFractionDigits()) {
420: throw new NumberFormatException(
421: "More digits AFTER the decimal separator than allowed:"
422: + proposedResult);
423: }
424:
425: // Now try to parse the field against the number format
426: try {
427: d = new Double(getNumberFormat().parse(
428: proposedResult).doubleValue());
429: } catch (ParseException pE) {
430: throw new NumberFormatException(
431: "Failed to parse. " + proposedResult
432: + pE.getMessage());
433: }
434: }
435: // Just use the default parse
436: else {
437: d = new Double(proposedResult);
438: }
439: }
440:
441: // Now determine if the number if within range
442: if ((d.doubleValue() >= min.doubleValue())
443: && (d.doubleValue() <= max.doubleValue())) {
444: // Now create the real type
445: if (min.getClass().equals(Byte.class)) {
446: return new Byte(d.byteValue());
447: } else {
448: if (min.getClass().equals(Short.class)) {
449: return new Short(d.shortValue());
450: } else {
451: if (min.getClass().equals(Integer.class)) {
452: return new Integer(d.intValue());
453: } else {
454: if (min.getClass().equals(Long.class)) {
455: return new Long(d.longValue());
456: } else {
457: if (min.getClass().equals(Float.class)) {
458: return new Float(d.floatValue());
459: } else {
460:
461: return d;
462: }
463: }
464: }
465: }
466: }
467: } else {
468: throw new NumberFormatException(d
469: + " Is out of range. Minimum is "
470: + min.doubleValue() + ", Maximum is "
471: + max.doubleValue());
472: }
473: }
474:
475: public void remove(int offs, int len)
476: throws BadLocationException {
477: if (!checkingEnabled) {
478: super .remove(offs, len);
479:
480: return;
481: }
482:
483: String currentText = getText(0, getLength());
484: String beforeOffset = currentText.substring(0, offs);
485: String afterOffset = currentText.substring(len + offs,
486: currentText.length());
487: String proposedResult = beforeOffset + afterOffset;
488:
489: try {
490: currentVal = parse(proposedResult);
491: super .remove(offs, len);
492: } catch (Exception e) {
493: }
494: }
495:
496: public void setMaximum(Number max) {
497: this .max = max;
498: }
499:
500: public void setMinimum(Number min) {
501: this.min = min;
502: }
503: }
504: }
|