/*
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.util;
import java.awt.Component;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
/**
* This class will actively check the inputs of 2 numbers in seperate text
* components. The number in the text components represent an upper and lower
* bound to some range. This class checks to make sure the user inputs values in
* the lower bound text field that are less than the value of the upper bound
* and vice versa for the upper bound text field. This class will also check to
* make sure the bounds fall within a given range if specified.
*
* The checks are automatically performed when the focus is lost on either
* component. If the inputs are correct then no event occurs. If the inputs are
* not correct, then a dialog message is displayed stating the reason why the
* bounds are invalid, and the original correct value is restored into the text
* components.
*
* @author rglista
* @author $Author: cgruber $
* @version $Revision: 904 $
*/
public class TextInputRangeChecker implements FocusListener {
protected static final int NONE = 0;
protected static final int LOWER = 1;
protected static final int UPPER = 2;
private JTextComponent lowerComponent;
private JTextComponent upperComponent;
private double maxRange;
private double lowerNumber;
private double upperNumber;
private Collection focusListeners;
private String invalidLowerMessage;
private String invalidUpperMessage;
private String invalidEitherMessage;
private String invalidRangeMessage;
/**
* Constructor with some of the settable parameters. No range checking is used.
*
* @param aLowerTextComponent A text component for the lower bound.
* @param anUpperTextComponent A text component for the upper bound.
*/
public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent) {
this(aLowerTextComponent, anUpperTextComponent, null, null, 0.0);
}
/**
* Constructor with some of the settable parameters. No range checking is used.
*
* @param aLowerTextComponent A text component for the lower bound.
* @param anUpperTextComponent A text component for the upper bound.
* @param lowerTextName The name of the lower bound, eg - start year.
* @param upperTextName The name of the upper bound, eg - end year. is
* used.
*/
public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent,
String lowerTextName, String upperTextName) {
this(aLowerTextComponent, anUpperTextComponent, lowerTextName, upperTextName, 0.0);
}
/**
* Constructor with some of the settable parameters.
*
* @param aLowerTextComponent A text component for the lower bound.
* @param anUpperTextComponent A text component for the upper bound.
* @param aMaxRange The range the bounds muist fall between, if 0
* then no range is used.
*/
public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent,
double aMaxRange) {
this(aLowerTextComponent, anUpperTextComponent, null, null, aMaxRange);
}
/**
* Constructor with all the settable parameters.
*
* @param aLowerTextComponent A text component for the lower bound.
* @param anUpperTextComponent A text component for the upper bound.
* @param lowerTextName The name of the lower bound, eg - start year.
* @param upperTextName The name of the upper bound, eg - end year.
* @param aMaxRange The range the bounds muist fall between, if 0
* then no range is used.
*/
public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent,
String lowerTextName, String upperTextName, double aMaxRange) {
lowerComponent = aLowerTextComponent;
upperComponent = anUpperTextComponent;
maxRange = aMaxRange;
focusListeners = new ArrayList(1); // For most cases, there will be only 1 listener.
lowerComponent.addFocusListener(this);
upperComponent.addFocusListener(this);
lowerNumber = getNumber(lowerComponent);
upperNumber = getNumber(upperComponent);
if ((lowerTextName != null) && (upperTextName != null)) {
invalidLowerMessage = "The " + lowerTextName + " must be less than or equal to the " + upperTextName + ".";
invalidUpperMessage = "The " + upperTextName + " must be greater than or equal to the " + lowerTextName
+ ".";
invalidEitherMessage = "The " + lowerTextName + " and/or the " + upperTextName + " are not correct.";
invalidRangeMessage = "The maximum range for the " + lowerTextName + " and " + upperTextName + " is "
+ maxRange + ".";
} else {
invalidLowerMessage = "The lower bound must be less than or equal to the upper bound.";
invalidUpperMessage = "The upper bound must be greater than or equal to the lower bound.";
invalidEitherMessage = "The upper and/or lower bounds are not correct.";
invalidRangeMessage = "The maximum range is " + maxRange + ".";
}
}
/**
* Allows the caller to perform the validation of the bounds programatically.
* The lower bound is compared to the upper bound and range checking is
* performed. If the lower bound is greater than the upper bound, or the range
* between the bounds is greater than the max range, then validation fails.
*
* @return TRUE is validation is successfull, FALSE if it fails.
*/
public boolean performCheck() {
return validate(null);
}
/**
* Adds the listener to the lists of focus listener maintened by this object.
* When one of the 2 text components receives a focus event, this object will
* fire that focus event to any of its listeners. This is useful when the
* calling object wants to be notified of the components focus events, but wants
* to ensure that the validation has occured first.
*
* NOTE: The focus is only fired if the validation was successful. This might
* have to be changed.
*
* @param aListener A Focus Listener to receive Focus Events.
*/
public void addFocusListener(FocusListener aListener) {
focusListeners.add(aListener);
}
/**
* Returns the last valid value of the lower bound. If this is called while the
* user is updating the text component but before the focus is lost, the value
* returned will be the original value before the user started updating the
* bound.
*
* @return The last valid value of the lower bound.
*/
public double getLastValidatedLowerNumber() {
return lowerNumber;
}
/**
* Returns the last valid value of the upper bound. If this is called while the
* user is updating the text component but before the focus is lost, the value
* returned will be the original value before the user started updating the
* bound.
*
* @return The last valid value of the upper bound.
*/
public double getLastValidatedUpperNumber() {
return upperNumber;
}
/**
* Method used to be notified when one of the text components has gained its
* focus.
*/
public void focusGained(FocusEvent e) {
lowerNumber = getNumber(lowerComponent);
upperNumber = getNumber(upperComponent);
}
/**
* Method used to be notified when one of the text components has lost its
* focus. Automatic validation occurs here.
*/
public void focusLost(FocusEvent e) {
if (e.isTemporary()) {
return;
}
if (validate(e.getSource())) {
fireFocusEvent(e);
}
}
/**
* Fires a focus lost event if the validation was successfull.
*/
protected void fireFocusEvent(FocusEvent e) {
for (Iterator it = focusListeners.iterator(); it.hasNext();) {
((FocusListener) it.next()).focusLost(e);
}
}
/**
* Validates the bounds inputed by the user.
*
* @param aComponent The component to use to display a dialog window, if
* neccessray. If null, then the parent window of the text
* componets will be used.
* @return TRUE if validation was successful, FALSE otherwise.
*/
protected boolean validate(Object aComponent) {
int componentUsed = NONE;
if (aComponent == lowerComponent) {
componentUsed = LOWER;
} else if (aComponent == upperComponent) {
componentUsed = UPPER;
}
double lower = getNumber(lowerComponent);
double upper = getNumber(upperComponent);
if (lower > upper) {
if (componentUsed == LOWER) {
lowerComponent.setText(Double.toString(lowerNumber));
displayMessage(invalidLowerMessage, lowerComponent);
} else if (componentUsed == UPPER) {
upperComponent.setText(Double.toString(upperNumber));
displayMessage(invalidUpperMessage, upperComponent);
} else {
upperComponent.setText(Double.toString(upperNumber));
lowerComponent.setText(Double.toString(lowerNumber));
displayMessage(invalidEitherMessage, lowerComponent.getTopLevelAncestor());
}
return false;
}
if (maxRange != 0.0) {
if ((upper - lower) > maxRange) {
if (componentUsed == LOWER) {
lowerComponent.setText(Double.toString(lowerNumber));
displayMessage(invalidRangeMessage, lowerComponent);
} else if (componentUsed == UPPER) {
upperComponent.setText(Double.toString(upperNumber));
displayMessage(invalidRangeMessage, upperComponent);
} else {
upperComponent.setText(Double.toString(upperNumber));
lowerComponent.setText(Double.toString(lowerNumber));
displayMessage(invalidRangeMessage, lowerComponent.getTopLevelAncestor());
}
return false;
}
}
lowerNumber = lower;
upperNumber = upper;
return true;
}
/**
* Creates a JOptionPane to display the reason why the bounds failed validation.
*/
protected void displayMessage(final String message, final Component parent) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(parent, message, "Data Entry Error", JOptionPane.ERROR_MESSAGE);
}
});
}
/**
* Gets the number represented in the text component. If the text does not
* represent a number, then zero is returned.
*/
protected double getNumber(JTextComponent aComponent) {
try {
return Double.valueOf(aComponent.getText()).doubleValue();
//1.2 return Double.parseDouble( aComponent.getText() );
} catch (NumberFormatException e) {
System.out.println(
"[GUI] TextInputRangeChecker.getNumber(): The text is NOT a number: " + aComponent.getText());
return 0.0;
}
}
}
/*
* $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven
* dependencies.
*
* Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in
* eclipse-friendly maven-enabled packages.
*
* Revision 1.2 2003/08/06 23:07:53 chochos general code cleanup (mostly,
* removing unused imports)
*
* Revision 1.1.1.1 2000/12/21 15:51:49 mpowers Contributing wotonomy.
*
* Revision 1.2 2000/12/20 16:25:46 michael Added log to all files.
*
*
*/