All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.cmu.tetradapp.util.ListDoubleTextField Maven / Gradle / Ivy

The newest version!
///////////////////////////////////////////////////////////////////////////////
// For information as to what this class does, see the Javadoc, below.       //
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,       //
// 2007, 2008, 2009, 2010, 2014, 2015, 2022 by Peter Spirtes, Richard        //
// Scheines, Joseph Ramsey, and Clark Glymour.                               //
//                                                                           //
// This program is free software; you can redistribute it and/or modify      //
// it under the terms of the GNU General Public License as published by      //
// the Free Software Foundation; either version 2 of the License, or         //
// (at your option) any later version.                                       //
//                                                                           //
// This program 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 General Public License for more details.                              //
//                                                                           //
// You should have received a copy of the GNU General Public License         //
// along with this program; if not, write to the Free Software               //
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA //
///////////////////////////////////////////////////////////////////////////////

package edu.cmu.tetradapp.util;

import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.text.NumberFormat;
import java.util.ArrayList;


/**
 * ListDoubleTextField is a custom JTextField class that is designed to display and edit comma-separated lists of double
 * values. It supports formatting and filtering of the input values.
 *
 * @author josephramsey
 */
public class ListDoubleTextField extends JTextField {

    /**
     * The getModel value of the text field.
     */
    private Double[] values;

    /**
     * The number formatter for the number displayed.
     */
    private NumberFormat format;

    /**
     * The number formatter for the number displayed (for small numbers).
     */
    private NumberFormat smallNumberFormat;

    /**
     * The cutoff below which (in absolute value) values will be displayed using the small number format.
     */
    private double smallNumberCutoff = 1e-4;

    /**
     * If set, filters the value input by the user. (Side effects are allowed.)
     */
    private Filter filter;

    /**
     * Creates a ListDoubleTextField component with the given parameters.
     *
     * @param values            the array of double values to be displayed and edited
     * @param width             the width of the text field in characters
     * @param format            the NumberFormat object used to format the values
     * @param smallNumberFormat the NumberFormat object used to format small values
     * @param smallNumberCutoff the cutoff value below which smallNumberFormat is used
     */
    public ListDoubleTextField(Double[] values, int width, NumberFormat format, NumberFormat smallNumberFormat,
                               double smallNumberCutoff) {
        super(width);
        setup(values, format, smallNumberFormat, smallNumberCutoff);
    }

    /**
     * Parses a comma-separated string of numbers and returns an array of Double values.
     *
     * @param actionCommand a String containing comma-separated numbers
     * @return an array of Double values parsed from the actionCommand string
     * @throws NullPointerException if the actionCommand is null
     */
    @NotNull
    public static Double[] getNumbers(String actionCommand) {
        String[] split = actionCommand.split(",");
        java.util.List values = new ArrayList<>();

        for (String s : split) {
            try {
                values.add(Double.parseDouble(s.trim()));
            } catch (NumberFormatException e) {
                // Skip.
            }
        }

        return values.toArray(new Double[0]);
    }

    /**
     * Retrieves the array of double values stored in the ListDoubleTextField.
     *
     * @return the array of double values
     */
    public Double[] getValues() {
        return this.values;
    }

    /**
     * Sets the value of the text field to the given double value. Should be overridden for more specific behavior.
     *
     * @param values the values to be set.
     */
    public void setValues(Double[] values) {
        Double[] newValues = filter(values, this.values);

        if (newValues.length > 0) {
            this.values = newValues;
        }

        smartSetText(this.format, this.values);
        firePropertyChange("newValue", null, this.values);
    }

    /**
     * Sets whether the given value should be accepted.
     *
     * @param filter a {@link ListDoubleTextField.Filter} object
     */
    public void setFilter(Filter filter) {
        this.filter = filter;
    }

    /**
     * Convinces the text field to stay the right size in layouts that are trying to expand it like a balloon by
     * returning the preferred size.
     *
     * @return the maximum size.
     */
    public Dimension getMaximumSize() {
        return getPreferredSize();
    }

    /**
     * Convinces the text field to stay the right size in layouts that are trying to shrink it.
     *
     * @return the maximum size.
     */
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

    /**
     * Filters the given value, returning the value that should actually be displayed. Typical use is to return either
     * the value or the old value, depending on whether the value is in range, though more complicated uses are
     * permitted. Side effects (such as storing the value in the process of filtering it) are permitted.
     *
     * @param values    The value entered by the user.
     * @param oldValues The value previously displayed, in case it needs to be reverted to.
     * @return The value that should be displayed.
     */
    private Double[] filter(Double[] values, Double[] oldValues) {
        if (this.filter == null) {
            return values;
        }

        return this.filter.filter(values, oldValues);
    }

    /**
     * Sets up the ListDoubleTextField with the given values and formats.
     *
     * @param values            the initial values for the ListDoubleTextField
     * @param nf                the format for displaying the values
     * @param smallNumberFormat the format for displaying small numbers
     * @param smallNumberCutoff the cutoff value for determining small numbers
     * @throws NullPointerException     if nf is null
     * @throws IllegalArgumentException if smallNumberCutoff is negative
     */
    private void setup(Double[] values, NumberFormat nf, NumberFormat smallNumberFormat, double smallNumberCutoff) {
        if (nf == null) {
            throw new NullPointerException();
        }

        if (smallNumberCutoff < 0.0) {
            throw new IllegalArgumentException("smallNumberCutoff must be non-negative");
        }

        double _default = Double.NaN;
        Double[] defaultValues = new Double[values.length];
        for (int i = 0; i < values.length; i++) {
            defaultValues[i] = _default;
        }

        this.values = filter(values, defaultValues);
        this.format = nf;
        this.smallNumberFormat = smallNumberFormat;
        this.smallNumberCutoff = smallNumberCutoff;
        smartSetText(nf, this.values);

        addActionListener(e -> {
            Double[] values1 = getNumbers(getText());
            Double[] filtered = filter(values1, values);

            if (filtered.length > 0) {
                setValues(filtered);
            } else {
                setValues(values);
            }
        });

        addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent e) {
                ListDoubleTextField source = (ListDoubleTextField) e.getSource();

                if (source.isEditable()) {
                    source.selectAll();
                }
            }

            public void focusLost(FocusEvent e) {
                Double[] values1 = getNumbers(getText());
                Double[] filtered = filter(values1, values);

                if (filtered.length > 0) {
                    setValues(filtered);
                } else {
                    setValues(values);
                }
            }
        });
    }

    /**
     * Sets the text of the component by formatting an array of Double values with the given NumberFormat. If a value is
     * null or NaN, it is skipped in the formatting process. Values smaller than the smallNumberCutoff are formatted
     * using the smallNumberFormat, while values equal or larger than the smallNumberCutoff are formatted using the
     * provided NumberFormat.
     *
     * @param nf     The NumberFormat to use for formatting the values equal or larger than the smallNumberCutoff
     * @param values The array of Double values to format
     */
    private void smartSetText(NumberFormat nf, Double[] values) {
        StringBuilder sb = new StringBuilder();
        java.util.List valueStrings = new ArrayList<>();

        for (Double aDouble : values) {
            if (aDouble != null) {
                double value = aDouble;

                if (!Double.isNaN(value) && !Double.isInfinite(value)) {
                    if (Math.abs(value) < this.smallNumberCutoff && value != 0.0) {
                        valueStrings.add(this.smallNumberFormat.format(value));
                    } else {
                        valueStrings.add(nf.format(value));
                    }
                }
            }
        }

        for (int i = 0; i < valueStrings.size(); i++) {
            sb.append(valueStrings.get(i));

            if (i < valueStrings.size() - 1) {
                sb.append(", ");
            }
        }

        setText(sb.toString());
    }

    /**
     * Filters the given value, returning the value that should actually be displayed. Typical use is to return either
     * the value or the old value, depending on whether the value is in range, though more complicated uses are
     * permitted. Side effects (such as storing the value in the process of filtering it) are permitted.
     */
    public interface Filter {

        /**
         * Filters the given value, returning the new value that should be displayed.
         *
         * @param value    The value entered by the user.
         * @param oldValue The value previously displayed, in case it needs to be reverted to.
         * @return The value that should be displayed.
         */
        Double[] filter(Double[] value, Double[] oldValue);
    }
}







© 2015 - 2025 Weber Informatics LLC | Privacy Policy