/*
This file is part of the software framework JStatCom,
a library for building applications for data based analysis.
Copyright (C) 2000-2005 Markus Kraetzig
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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
*/
package com.jstatcom.component;
import java.text.NumberFormat;
import com.jstatcom.model.NumberRange;
import com.jstatcom.model.NumberRangeTypes;
import com.jstatcom.util.UMath;
import com.jstatcom.util.UString;
/**
* This component allows to input a range specified by two komma separated numbers
* that is automatically checked according to an overall NumberRange object.
* The overall range to validate against can be specified by constants.
*
* Example text input would be 1, 100. Only real numbers are allowed,
* +/- Infinity or NaN are always invalid. This component always assumes that the
* range type of the selected range is CLOSED, even if a different
* range was set via the setNumberRange method. Therefore
* getNumberRange always returns a CLOSED range.
* The overall range does not have this restriction imposed.
*
* Usage Note:
* If the number format type is set to NumberFormatTypes.DEFAULT, then
* the precision set for this selector defines, how displayed numbers are rounded.
* Any additional precision is lost, after a number was selected. If, for example,
* only int numbers should be possible, the precision can be set to 0,
* which is also the default. The resulting number is then stored as a
* double that is equal to an int.
* If the number format type is set to NumberFormatTypes.SCIENTIFIC,
* then the precision is used only for the display. No rounding is performed and
* a precision of 0 does not mean that values are equal to an int.
*
* This class fires a "numberRange" property change, if a new number range was set.
*
* @author Markus Kraetzig
* @see com.jstatcom.model.NumberRange
*/
public final class NumRangeSelector extends IValTextField {
private int precision = 0;
private NumberFormat numberFormat = NumberFormatTypes.DEFAULT.getInstance();
private NumberFormatTypes numberFormatType = NumberFormatTypes.DEFAULT;
private NumberRange currentNumberRange = NumberRange.valueOf("[1,100]");
private NumberRange overallNumberRange = null;
private final NumberRange defaultOverallRange =
new NumberRange(
Double.NEGATIVE_INFINITY,
Double.POSITIVE_INFINITY,
NumberRangeTypes.OPEN);
/**
* Default constructor initializes
* overall range to (-Infinity,+Infinity) and sets the displayed
* range to value to 1, 100.
*/
public NumRangeSelector() {
super();
setOverallNumberRange(defaultOverallRange);
setBorder(MatrixBorder.getInstanceSmall());
setSize(100, 20);
}
/**
* Gets the current number format type.
*
* @return the format type in use
*/
public NumberFormatTypes getNumberFormatType() {
return numberFormatType;
}
/**
* Gets the currently displayed number range. This range is always
* of type CLOSED an can never have +/- Infinity bounds.
*
* @return the (closed) selected number range
*/
public NumberRange getNumberRange() {
return currentNumberRange;
}
/**
* Gets the overall range that the range input is checked against.
*
* @return current overall number range
*/
public NumberRange getOverallNumberRange() {
return overallNumberRange;
}
/**
* Gets the current overall number range displayed as a string, for
* example [1,100] or ("lowerBound_p", 20].
*
* @return string representation of overall number range
*/
public String getOverallRangeExpr() {
return overallNumberRange.toString();
}
/**
* Gets the precision for the displayed numbers.
*
* @return number count right to the decimal point
*/
public int getPrecision() {
return precision;
}
/**
* Error handling routine if validation failed. Displays error message
* and sets back number value to the last valid one..
*
* @param msg an error message
*/
public void handleError(String msg) {
super.handleError(
msg
+ "\nThe previous value "
+ currentNumberRange
+ " is restored.");
setNumberRange(currentNumberRange);
}
/**
* Sets the current number format type.
*
* @param newNumberFormatType format type to use
*/
public void setNumberFormatType(
com.jstatcom.component.NumberFormatTypes newNumberFormatType) {
numberFormatType = newNumberFormatType;
numberFormat = newNumberFormatType.getInstance();
}
/**
* Sets the currently displayed number range with checking whether it conforms
* to the specified overall range. The number range is always set to range type
* CLOSED, even if newRange has a different type.
* This is possible, because any legal range can also be a closed range. Be aware
* that +/- Infinity is not allowed for the selected range bounds.
* This method fires a "numberRange" property change with the old value and the changed value,
* if a new number range was set.
*
* @param newRange the new selected number range
* @throws IllegalArgumentException if newRange is not enclosed
* in overall range
*/
public void setNumberRange(NumberRange newRange) {
String msg;
if ((msg = overallNumberRange.encloses(newRange)) != null)
throw new IllegalArgumentException(msg);
NumberRange oldRange = currentNumberRange;
// Always set selected range to type CLOSED.
if (newRange.rangeType == NumberRangeTypes.CLOSED)
currentNumberRange = newRange;
else
currentNumberRange =
new NumberRange(
newRange.lowerBound,
newRange.upperBound,
NumberRangeTypes.CLOSED);
setTextCarefully(
UString.formatNumber(
currentNumberRange.lowerBound,
numberFormat,
precision)
+ ", "
+ UString.formatNumber(
currentNumberRange.upperBound,
numberFormat,
precision));
if (!oldRange.equals(newRange))
firePropertyChange("numberRange", oldRange, newRange);
}
/**
* Sets the overall range that the range input is checked against.
* The currently displayed range is automatically validated
* against the new overall range. If the current range is not valid,
* it automatically set to a legal value within
* newNumberRange.
*
* @param newNumberRange the overall number range for validation,
* if null then a range of (-Infinity,+Infinity)
* is set.
*/
public void setOverallNumberRange(NumberRange newNumberRange) {
if (newNumberRange == null)
newNumberRange = new NumberRange();
// default constructor creates(-inf,inf)
overallNumberRange = newNumberRange;
if (overallNumberRange.encloses(currentNumberRange) == null) {
setNumberRange(currentNumberRange); // number is valid
return;
}
// set to default range in case of problem
setNumberRange(overallNumberRange.defaultNumberRange());
}
/**
* Sets the range to be checked against by specifying a
* string of the form ["(", "["] lowerBound "," upperBound + ["]", ")"],
* where lowerbound/upperBound are either a number or a
* symbolname, for example
* [lowerBound_p, upperBound_p] or (0, upperBound_x] or [1,10].
*
* If the range is specified without symbol names, it is set
* immediately. If the range depends on symbols to be loaded from
* a symbol table, this is postponed to the next FOCUS_GAINED event.
* In the meantime, the default non-validating (-Infinity,Infinity)
* range is used.
*
* @param expr the expression to be parsed
* @throws IllegalArgumentException if expr == null
* or if it cannot be parsed to a valid range
*/
public void setOverallRangeExpr(String expr) {
if (expr == null || expr.length() == 0)
throw new IllegalArgumentException("Range expression is empty.");
setOverallNumberRange(NumberRange.valueOf(expr));
}
/**
* Sets number of digits right to decimal point.
*
* @param newPrecision new number precision
* @throws IllegalArgumentException if newPrecision < 0
*/
public void setPrecision(int newPrecision) {
if (newPrecision < 0)
throw new IllegalArgumentException("Negative precision argument.");
this.precision = newPrecision;
}
/**
* Sets the currently selected range by specifying a
* string with lower bound and upper bound seperated by
* a komma, for example 1, 100.
* Symbol names are not allowed for the bounds but have to be
* specified separately by the respective accessor methods.
*
* @param expr string containing komma separated bounds
* @throws IllegalArgumentException if bounds are not enclosed
* in overall range or expr could not be parsed to a
* valid range of type CLOSED
*/
public void setRangeBounds(String expr) {
if (expr == null || expr.length() == 0)
throw new IllegalArgumentException("Range expression is empty.");
expr =
NumberRangeTypes.CLOSED.leftBracket
+ expr
+ NumberRangeTypes.CLOSED.rightBracket;
setNumberRange(NumberRange.valueOf(expr));
}
/**
* Validates input against the specified range whenever necessary.
* This method is invoked by the superclass and does not need to be
* called manually.
*
* @return null if ok, an error message otherwise
*/
public String validateInput() {
String exp = getText().trim();
String error = "Input \"" + exp + "\" is not a valid number range.";
// Split input in 2 parts, separated by a komma
// and parse single number strings afterwards.
int kommaIndex = exp.indexOf(",");
if (kommaIndex < 1 || kommaIndex > exp.length() - 2)
// No komma within string, cannot be a valid range.
throw new IllegalArgumentException(error);
String lString = exp.substring(0, kommaIndex);
String uString = exp.substring(kommaIndex + 1, exp.length());
Double lBound = UString.parseToNumber(lString);
Double uBound = UString.parseToNumber(uString);
if (lBound == null || uBound == null)
return "Input \"" + exp + "\" is not a valid number range.";
double lower = lBound.doubleValue();
double upper = uBound.doubleValue();
if (numberFormatType == NumberFormatTypes.DEFAULT)
lower = UMath.round(lower, precision);
upper = UMath.round(upper, precision);
NumberRange newRange =
new NumberRange(lower, upper, NumberRangeTypes.CLOSED);
error = overallNumberRange.encloses(newRange);
if (error == null) {
setNumberRange(newRange); // number range is valid
return null;
}
return newRange.formattedString(numberFormat)
+ " is not contained in "
+ overallNumberRange.formattedString(numberFormat)
+ ".";
}
}
Ads help maintain this website.