/* * 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); } } } }