/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Blacksmith, Inc This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.ui.swing.components; /** * NumericTextField is a "smart" text field that restricts the user's input. The * input is restructed to numeric only wich can be of two types: integer and * real numbers. A range can also be placed on the text field. The default type * is integer, with the being (Integer.MIN_VALUE, Integer.MAX_VALUE). * * @author rob@straylight.princeton.com * @author $Author: cgruber $ * @version $Revision: 893 $ */ public class NumericTextField extends SmartTextField { /******************************* * CONSTANTS *******************************/ /** * Restrict the text input to integers (whole numbers) only. */ public final static int INTEGER = 0; /** * Restrict the text input to floating-point numbers only. */ public final static int FLOAT = 1; private Number maximumValue = null; private Number minimumValue = null; private boolean sign = false; private int newCaretPosition = 0; private int valueType = INTEGER; /******************************* * PUBLIC METHODS *******************************/ /** * Default constructor. */ public NumericTextField() { this("", 0); } /** * Constructor. * * @param text The initial string the text field is set to. */ public NumericTextField(String text) { this(text, 0); } /** * Constructor. * * @param columns Width of the text field (in characters). */ public NumericTextField(int columns) { this("", columns); } /** * Constructor. * * @param text The initial string the text field is set to. * @param columns Width of the text field (in characters). */ public NumericTextField(String text, int columns) { super(text, columns); } /** * Sets the upper limit of the range of numbers to accept. * * @param newMaximumValue The maximum number accepted by the text field. */ public void setMaximumValue(double newMaximumValue) { if (newMaximumValue >= 0) { maximumValue = new Double(newMaximumValue); } else { maximumValue = null; } } /** * Returns the upper limit of the range of numbers to accept. * * @return The maximum number accepted by this text field. */ public double getMaximumValue() { if (valueType == INTEGER) { return (maximumValue == null) ? (double) Integer.MAX_VALUE : maximumValue.doubleValue(); } else { return (maximumValue == null) ? Double.MAX_VALUE : maximumValue.doubleValue(); } } /** * Sets the lower limit of the range of numbers to accept. * * @param newMinimumValue The minimum number accepted by the text field. */ public void setMinimumValue(double newMinimumValue) { if (newMinimumValue <= 0) { minimumValue = new Double(newMinimumValue); } else { minimumValue = null; } } /** * Returns the lower limit of the range of numbers to accept. * * @return The minimum number accepted by this text field. */ public double getMinimumValue() { if (valueType == INTEGER) { return (minimumValue == null) ? (double) Integer.MIN_VALUE : minimumValue.doubleValue(); } else { return (minimumValue == null) ? -1.0 * Double.MAX_VALUE : minimumValue.doubleValue(); // NOTE: Double.MIN_VALUE returns the smallest positive value - oooops. } } /** * Sets which type of number this text field can accept. * * @see #INTEGER * @see #FLOAT * @param newValueType The type of number to accept. */ public void setValueType(int newValueType) { if ((newValueType != INTEGER) && (newValueType != FLOAT)) { valueType = INTEGER; } else { valueType = newValueType; } } /** * Returns which type of number this text field accepts. The default is integer. * * @see #INTEGER * @see #FLOAT * @return The type of number to accept. */ public int getValueType() { return valueType; } /** * Returns the integer numeric value of the string in the text field. The type * can be either integer of float. * * @return The current value in the text field. */ public int getIntValue() { int value = 0; try { value = Integer.parseInt(getText()); } catch (NumberFormatException e) { try { Double dValue = Double.valueOf(getText()); value = dValue.intValue(); } catch (NumberFormatException ignored) { } } return value; } /** * Sets the text field to integer value specified. * * @param aValue An integer value to display in the text field. */ public void setIntValue(int aValue) { setText(Integer.toString(aValue)); } /** * Returns the real number numeric value of the string in the text field. The * type can be either integer of float. * * @return The current value in the text field. */ public double getDoubleValue() { Double value = new Double(0); try { value = Double.valueOf(getText()); } catch (NumberFormatException ignored) { } return value.doubleValue(); } /** * Sets the text field to the double value specified. If the text field type is * FLOAT then the the number is display as a real number. If the text field type * is INTEGER then the number is converted to a whole number for displaying. * * @param aValue A double value to display in the text field. */ public void setDoubleValue(double aValue) { Double temp = new Double(aValue); if (valueType == FLOAT) { setText(temp.toString()); } else { setText(Integer.toString(temp.intValue())); } } /******************************* * PROTECTED METHODS *******************************/ protected boolean isValidCharacter(char aChar) { if (((aChar >= ' ') && (aChar <= '/')) || ((aChar >= ':') && (aChar <= '~'))) { if (aChar == '.') { if (valueType == FLOAT) { return true; } else { return false; } } else if (aChar == '-') { if (getMinimumValue() < 0) { return true; } else { return false; } } else if (aChar == '+') { if (getMaximumValue() >= 0) { return true; } else { return false; } } return false; } return true; } protected boolean isValidString(String aString) { int iValue = 0; double dValue = 0.0; String tempString = new String(scanForSignChar(aString)); if (valueType == INTEGER) { try { iValue = Integer.parseInt(tempString); } catch (NumberFormatException e1) { if ((tempString.compareTo("-") == 0) && (getMinimumValue() < 0.0)) { iValue = 0; } else { return false; } } if ((((double) iValue) < getMinimumValue()) || (((double) iValue) > getMaximumValue())) { return false; } } else { // Double.valueOf requires a zero before the decimal point if (tempString.startsWith(".")) { tempString = "0" + tempString; } try { dValue = Double.valueOf(tempString).doubleValue(); } catch (NumberFormatException e2) { if ((tempString.compareTo("-") == 0) && (getMinimumValue() < 0.0)) { dValue = 0.0; } else { return false; } } if ((dValue < getMinimumValue()) || (dValue > getMaximumValue())) { return false; } } return true; } protected void postProcessing() { if (sign) { setText(scanForSignChar(getText())); setCaretPosition(newCaretPosition); } sign = false; } /******************************* * PRIVATE METHODS *******************************/ private String scanForSignChar(String aString) { String newString = ""; boolean positive = false; boolean negative = false; int oldCaretPosition = getCaretPosition(); int charactersAdded = 0; newCaretPosition = 0; if (aString.length() <= 0) { return aString; } for (int i = 0; i < aString.length(); ++i) { switch (aString.charAt(i)) { case '+': positive = true; break; case '-': negative = true; break; default: newString += aString.charAt(i); charactersAdded++; break; } if ((i + 1) == oldCaretPosition) { newCaretPosition = charactersAdded; } } if ((!(positive)) && (negative)) { newString = "-" + newString; newCaretPosition++; } if (positive || negative) { sign = true; } return newString; } }