/*
* RealNumberField.java
*
* Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST 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
* of the License, or (at your option) any later version.
*
* BEAST 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 BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package dr.app.gui.components;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
public class RealNumberField extends JTextField implements FocusListener, DocumentListener {
public static String NaN = "NaN";
public static String POSITIVE_INFINITY = "+INF";
public static String NEGATIVE_INFINITY = "-INF";
public static String MAX_VALUE = "MAX";
public static String MIN_VALUE = "MIN";
protected static char MINUS = '-';
protected static char PERIOD = '.';
protected EventListenerList changeListeners = new EventListenerList();
protected double min;
protected double max;
protected final boolean includeMin;
protected final boolean includeMax;
protected boolean range_check = false;
protected boolean range_checked = false;
protected String label; // make sensible error message
private boolean isValueValid = true;
protected boolean allowEmpty = false;
public RealNumberField() { // no FocusListener
super();
setLabel("Value");
includeMin = true;
includeMax = true;
}
public RealNumberField(double min, double max) {
this(min, max, "Value");
this.addFocusListener(this);
}
public RealNumberField(double min, double max, String label) { // no FocusListener
this(min, true, max, true, label);
}
public RealNumberField(double min, boolean includeMin, double max, boolean includeMax, String label) { // no FocusListener
super();
this.min = min;
this.max = max;
this.includeMin = includeMin;
this.includeMax = includeMax;
setLabel(label);
range_check = true;
}
public void setAllowEmpty(boolean allowEmpty) {
this.allowEmpty = allowEmpty;
}
public void focusGained(FocusEvent evt) {
}
public void focusLost(FocusEvent evt) {
validateField();
}
public void validateField() {
if (range_check && !range_checked) {
range_checked = true;
isValueValid = isValueValid();
if (!isValueValid) {
displayErrorMessage();
// regain focus for this component
this.requestFocus();
}
}
}
public boolean isValueValid() {
if (getText().trim().equals("") && allowEmpty) {
return true;
}
if (range_check) {
try {
double value = getValue();
if (value < min || value > max) {
return false;
}
if (!includeMin && value == min) {
return false;
}
if (!includeMax && value == max) {
return false;
}
} catch (NumberFormatException e) {
return false;
}
}
return true;
}
public void setText(Double value) {
if (value == null && allowEmpty) {
setText("");
}
if (value == Double.NaN) {
setText(NaN);
} else if (value == Double.POSITIVE_INFINITY) {
setText(POSITIVE_INFINITY);
} else if (value == Double.NEGATIVE_INFINITY) {
setText(NEGATIVE_INFINITY);
} else if (value == Double.MAX_VALUE) {
setText(MAX_VALUE);
} else if (value == Double.MIN_VALUE) {
setText(MIN_VALUE);
} else {
setText(Double.toString(value));
}
}
public void setText(Integer obj) {
setText(obj.toString()); // where used?
}
public void setText(Long obj) {
setText(obj.toString()); // where used?
}
public String getErrorMessage() {
String message = "";
if (min == Double.MIN_VALUE) {
message = " greater than 0";
} else if (!Double.isInfinite(min) && min != -Double.MAX_VALUE) {
message = " greater than " + min;
}
if (max == -Double.MIN_VALUE) {
message = " less than 0";
} else if (!Double.isInfinite(max) && max != Double.MAX_VALUE) {
if (message.length() > 0) {
message += " and";
}
message = " less than " + max;
}
return label + " must be" + message;
}
private void displayErrorMessage() {
JOptionPane.showMessageDialog(null,
getErrorMessage(), "Invalid value", JOptionPane.ERROR_MESSAGE);
}
public void setRange(double min, double max) {
this.min = min;
this.max = max;
range_check = true;
}
public void setValue(double value) {
if (range_check) {
if (value < min || value > max) {
displayErrorMessage();
return;
}
if (!includeMin && value == min) {
displayErrorMessage();
return;
}
if (!includeMax && value == max) {
displayErrorMessage();
return;
}
}
setText(value);
}
public void setLabel(String label) {
this.label = label;
}
public Double getValue() {
try {
if (allowEmpty && getText().trim().equals("")) {
return null;
} else if (getText().equals(POSITIVE_INFINITY)) {
return Double.POSITIVE_INFINITY;
} else if (getText().equals(NEGATIVE_INFINITY)) {
return Double.NEGATIVE_INFINITY;
} else if (getText().equals(MAX_VALUE)) {
return Double.MAX_VALUE;
} else if (getText().equals(MIN_VALUE)) {
return Double.MIN_VALUE;
} else if (getText().equals(NaN)) {
return Double.NaN;
} else {
// System.out.println("=" + getText() + "=");
return new Double(getText());
}
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(null, "Unable to parse number correctly",
"Number Format Exception",
JOptionPane.ERROR_MESSAGE);
isValueValid = false;
return null;
}
}
protected Document createDefaultModel() {
Document doc = new RealNumberField.RealNumberFieldDocument();
doc.addDocumentListener(this);
return doc;
}
public void insertUpdate(DocumentEvent e) {
fireChanged();
}
public void removeUpdate(DocumentEvent e) {
fireChanged();
}
public void changedUpdate(DocumentEvent e) {
fireChanged();
}
static char[] numberSet = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
};
class RealNumberFieldDocument extends PlainDocument {
public void insertString(int offs, String str, AttributeSet a)
throws BadLocationException {
if (str == "" || str == null) return;
if (str.equals("+INF") || str.equals("-INF") || str.equals("NaN")
|| str.equals("MAX_VALUE") || str.equals("MIN_VALUE")) {
super.insertString(offs, str, a);
return;
}
str = str.trim();
int length = getLength();
String buf = getText(0, offs) + str + getText(offs, length - offs);
buf = buf.trim().toUpperCase();
char[] array = buf.toCharArray();
if (array.length > 0) {
if (array[0] != MINUS && !member(array[0], numberSet) &&
array[0] != PERIOD) {
Toolkit.getDefaultToolkit().beep();
return;
}
}
boolean period_found = (array.length > 0 && array[0] == PERIOD);
boolean exponent_found = false;
int exponent_index = -1;
boolean exponent_sign_found = false;
for (int i = 1; i < array.length; i++) {
if (!member(array[i], numberSet)) {
if (!period_found && array[i] == PERIOD) {
period_found = true;
} else if (!exponent_found && array[i] == 'E') {
exponent_found = true;
exponent_index = i;
} else if (exponent_found && i == (exponent_index + 1) && !exponent_sign_found && array[i] == '-') {
exponent_sign_found = true;
} else {
Toolkit.getDefaultToolkit().beep();
return;
}
}
}
super.insertString(offs, str, a);
}
}
static boolean member(char item, char[] array) {
for (char anArray : array) {
if (anArray == item) {
return true;
}
}
return false;
}
//------------------------------------------------------------------------
// Event Methods
//------------------------------------------------------------------------
public void addChangeListener(ChangeListener x) {
changeListeners.add(ChangeListener.class, x);
}
public void removeChangeListener(ChangeListener x) {
changeListeners.remove(ChangeListener.class, x);
}
protected void fireChanged() {
range_checked = false;
isValueValid = true;
ChangeEvent c = new ChangeEvent(this);
Object[] listeners = changeListeners.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ChangeListener.class) {
ChangeListener cl = (ChangeListener) listeners[i + 1];
cl.stateChanged(c);
}
}
}
}