pipeline.parameter_cell_views.FloatRangeSlider.java Source code

Java tutorial

Introduction

Here is the source code for pipeline.parameter_cell_views.FloatRangeSlider.java

Source

/*******************************************************************************
 * Parismi v0.1
 * Copyright (c) 2009-2015 Cinquin Lab.
 * All rights reserved. This code is made available under a dual license:
 * the two-clause BSD license or the GNU Public License v2.
 ******************************************************************************/
package pipeline.parameter_cell_views;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.NumberFormat;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.apache.commons.lang3.text.WordUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.IntervalMarker;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;

import pipeline.misc_util.Utils;
import pipeline.misc_util.Utils.LogLevel;
import pipeline.parameters.AbstractParameter;
import pipeline.parameters.FloatRangeParameter;
import pipeline.parameters.ParameterListener;

public class FloatRangeSlider extends AbstractParameterCellView
        implements MouseListener, MouseMotionListener, ParameterListener {

    private static final long serialVersionUID = 1L;

    private RangeSlider slider;
    private JTextField textMinimum;
    private JTextField textMaximum;
    private JTextField currentTextValue0, currentTextValue1;
    private JLabel parameterName;
    private JButton resetMin, resetMax, resetRange;
    private JTable table;
    private JPanel panelForHistogram;
    private int tableRow;

    private float maximum;
    private float minimum;
    private float currentValue0, currentValue1;
    private JPanel textValueFrame;

    private FloatRangeParameter currentParameter;

    private boolean silenceUpdate;

    private NumberFormat nf = NumberFormat.getInstance();

    private GridBagConstraints cForHistogram;

    @Override
    protected NumberFormat getNumberFormatter() {
        return nf;
    }

    public FloatRangeSlider() {
        super();
        addMouseWheelListener(e -> {

            int rotation = e.getWheelRotation();

            float[] float_values = (float[]) (currentParameter.getValue());
            currentValue0 = float_values[0];
            currentValue1 = float_values[1];
            minimum = float_values[2];
            maximum = float_values[3];

            float change = (currentValue1 - currentValue0 + 1) * rotation * Utils.getMouseWheelClickFactor();

            currentValue0 += change;
            currentValue1 += change;

            if (!((e.getModifiers() & java.awt.event.InputEvent.ALT_MASK) > 0)) {
                if (currentValue1 > maximum) {
                    float difference = currentValue1 - currentValue0;
                    currentValue1 = maximum;
                    currentValue0 = currentValue1 - difference;
                }
                if (currentValue0 < minimum) {
                    float difference = currentValue1 - currentValue0;
                    currentValue0 = minimum;
                    currentValue1 = currentValue0 + difference;
                }
            }

            currentParameter.setValue(new float[] { currentValue0, currentValue1, minimum, maximum });

            readInValuesFromParameter();
            updateDisplays();
            currentParameter.fireValueChanged(false, false, true);
        });
        nf.setGroupingUsed(true);
        nf.setMaximumFractionDigits(5);
        nf.setMaximumIntegerDigits(10);

        setLayout(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.BOTH;

        c.gridx = 0;
        c.gridy = 0;
        c.weighty = 1.0;
        c.weightx = 1.0;
        c.gridwidth = 4;

        cForHistogram = (GridBagConstraints) c.clone();

        panelForHistogram = new JPanel();
        panelForHistogram.setPreferredSize(new Dimension(200, 150));
        panelForHistogram.setLayout(new BorderLayout());

        c.gridx = 0;
        c.gridy = 1;// 1
        c.weighty = 0.0;
        c.weightx = 0.0;
        c.gridwidth = 4;
        add(Box.createRigidArea(new Dimension(0, 5)), c);

        slider = new RangeSlider(0, 20);
        slider.addChangeListener(new sliderListener());
        c.gridx = 0;
        c.gridy = 2;
        c.weighty = 0.0;
        c.weightx = 0.0;
        c.gridwidth = 4;
        add(slider, c);

        c.gridx = 0;
        c.gridy = 3;
        c.weighty = 0.0;
        c.weightx = 1.0;
        c.gridwidth = 4;
        Component comp = Box.createRigidArea(new Dimension(0, 10));
        ((JComponent) comp).setOpaque(true);
        add(comp, c);
        c.gridwidth = 1;

        final textBoxListener minMaxListener = new textBoxListener();

        currentTextValue0 = new JTextField("");
        currentTextValue1 = new JTextField("");
        currentTextValue0.addActionListener(new textBoxListenerTriggersUpdate());
        currentTextValue1.addActionListener(new textBoxListenerTriggersUpdate());
        Font smallerFont = new Font(currentTextValue0.getFont().getName(), currentTextValue0.getFont().getStyle(),
                currentTextValue0.getFont().getSize() - 2);
        textMinimum = new JTextField("0");
        textMinimum.setFont(smallerFont);
        textMinimum.addActionListener(minMaxListener);
        textMaximum = new JTextField("50");
        textMaximum.setFont(smallerFont);
        textMaximum.addActionListener(minMaxListener);
        textMaximum.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                minMaxListener.actionPerformed(new ActionEvent(textMaximum, 0, ""));
            }
        });
        textMinimum.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                minMaxListener.actionPerformed(new ActionEvent(textMinimum, 0, ""));
            }
        });

        textValueFrame = new JPanel();
        textValueFrame.setBackground(getBackground());
        textValueFrame.setLayout(new GridBagLayout());

        c.gridx = 0;
        c.gridy = 0;
        c.weighty = 1.0;
        c.weightx = 0.1;
        textValueFrame.add(textMinimum, c);

        c.gridx = 1;
        c.gridy = 0;
        c.weighty = 1.0;
        c.weightx = 0.3;
        textValueFrame.add(currentTextValue0, c);

        c.gridx = 2;
        c.gridy = 0;
        c.weighty = 1.0;
        c.weightx = 0.3;
        textValueFrame.add(currentTextValue1, c);

        c.gridx = 3;
        c.gridy = 0;
        c.weighty = 1.0;
        c.weightx = 0.1;
        textValueFrame.add(textMaximum, c);

        c.gridx = 0;
        c.gridy = 4;
        c.weighty = 0.0;
        c.weightx = 0.3;
        c.gridwidth = 4;
        add(textValueFrame, c);
        c.gridwidth = 1;

        parameterName = new JLabel("parameter");
        c.gridx = 0;
        c.gridy = 5;
        c.weighty = 0.0;
        c.weightx = 0.01;
        c.gridwidth = 1;
        add(parameterName, c);

        resetMin = new JButton("Min");
        resetMin.setActionCommand("Reset Min");
        resetMin.addActionListener(new buttonListener());
        resetMax = new JButton("Max");
        resetMax.setActionCommand("Reset Max");
        resetMax.addActionListener(new buttonListener());
        resetRange = new JButton("MinMax");
        resetRange.setActionCommand("Reset Range");
        resetRange.addActionListener(new buttonListener());

        c.gridx = 1;
        c.gridy = 5;
        c.weighty = 0.0;
        c.weightx = 0.2;
        c.gridwidth = 1;
        add(resetMin, c);

        c.gridx = 2;
        c.gridy = 5;
        c.weighty = 0.0;
        c.weightx = 0.2;
        c.gridwidth = 1;
        add(resetMax, c);

        c.gridx = 3;
        c.gridy = 5;
        c.weighty = 0.0;
        c.weightx = 0.2;
        c.gridwidth = 1;
        add(resetRange, c);
        // ,resetMax,resetRange;

    }

    private class buttonListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            currentParameter.buttonPressed(e.getActionCommand(), false, null);
            // now update text boxes and slider in "silenced" mode
            float[] float_values = (float[]) (currentParameter.getValue());
            silenceUpdate = true;

            currentValue0 = float_values[0];
            currentValue1 = float_values[1];
            minimum = float_values[2];
            maximum = float_values[3];
            updateDisplayAndParameter(false, false);

            parameterName.setText(currentParameter.getParamNameDescription()[0]);
            parameterName.setVisible(!currentParameter.getParamNameDescription()[0].equals(""));
            textMinimum.setEditable(currentParameter.editable()[0]);
            textMaximum.setEditable(currentParameter.editable()[1]);
            setToolTipText(
                    Utils.encodeHTML(WordUtils.wrap(currentParameter.getParamNameDescription()[1], 50, null, true))
                            .replace("\n", "<br>\n"));
            if (table != null) {
                int height_wanted = (int) getPreferredSize().getHeight();
                if (height_wanted > table.getRowHeight(tableRow))
                    table.setRowHeight(tableRow, height_wanted);
            }
            silenceUpdate = false;
        }
    }

    private class textBoxListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            if (silenceUpdate)
                return;
            JTextField source = (JTextField) e.getSource();
            try {
                minimum = parseTextBox(textMinimum).floatValue();
                maximum = parseTextBox(textMaximum).floatValue();
                currentParameter.updateBounds(minimum, maximum);
                readInValuesFromParameter();
                currentParameter.setValue(new float[] { currentValue0, currentValue1, minimum, maximum });
                // Utils.log("Changed values to "+minimum+"-"+maximum+", "+currentValue0+"-"+currentValue1,LogLevel.VERBOSE_DEBUG);
                updateDisplayAndParameter(false, false);
            } catch (NumberFormatException nfe) {
                Utils.log("cant parse something, maybe " + source.getText() + ", as a float; ignoring",
                        LogLevel.WARNING);
            }
        }
    }

    private class textBoxListenerTriggersUpdate implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            if (silenceUpdate)
                return;
            JTextField source = (JTextField) e.getSource();
            try {
                currentValue0 = parseTextBox(currentTextValue0).floatValue();
                currentValue1 = parseTextBox(currentTextValue1).floatValue();
                // Utils.log("Changed values to "+minimum+"-"+maximum+", "+currentValue0+"-"+currentValue1,LogLevel.VERBOSE_DEBUG);
                currentParameter.setValue(new float[] { currentValue0, currentValue1, minimum, maximum });
                readInValuesFromParameter();
                updateDisplayAndParameter(true, false);

            } catch (NumberFormatException nfe) {
                Utils.log("cant parse something, maybe " + source.getText() + ", as a float; ignoring",
                        LogLevel.WARNING);
            }
        }
    }

    private class sliderListener implements ChangeListener {

        @Override
        public void stateChanged(ChangeEvent e) {
            if (silenceUpdate) {
                return;
            }
            currentValue0 = (slider.getValue()) / 100f;
            currentValue1 = (slider.getUpperValue()) / 100f;
            updateDisplayAndParameter(true, slider.getValueIsAdjusting());
        }
    }

    /**
     * Updates a text field displaying a number
     * 
     * @param f
     *            JTextField displaying the number
     * @param v
     *            Number to format and display
     */
    private void updateTextValue(JTextField f, float v) {
        f.setText("" + nf.format(v));
    }

    private boolean evenTableRow;
    private ChartPanel chartPanel;
    private JFreeChart chart;
    private IntervalMarker selectionRange;

    /**
     * Called whenever currentValue0, currentValue1, minimum, or maximum have been updated.
     * Updates all elements in the GUI.
     * 
     * @param triggerParameterUpdate
     *            Trigger a callback to the parameter and therefore to the pipeline
     * @param stillChanging
     *            True if parameter is still changing (ie if the user is still dragging the slider of the histogram
     *            interval marker)
     */
    private void updateDisplayAndParameter(boolean triggerParameterUpdate, boolean stillChanging) {
        if (silenceUpdate)
            return;
        currentParameter.setValue(new float[] { currentValue0, currentValue1, minimum, maximum });

        silenceUpdate = true;

        updateDisplays();

        silenceUpdate = false;
        if (triggerParameterUpdate)
            currentParameter.fireValueChanged(stillChanging, false, true);
    }

    private void readInValuesFromParameter() {
        float[] float_values = (float[]) (currentParameter.getValue());
        currentValue0 = float_values[0];
        currentValue1 = float_values[1];
        minimum = float_values[2];
        maximum = float_values[3];
    }

    private void updateDisplays() {
        boolean saveSilenceUpdate = silenceUpdate;
        silenceUpdate = true;
        readInValuesFromParameter();
        slider.setValue((int) (currentValue0 * 100f));
        slider.setUpperValue((int) (currentValue1 * 100f));
        slider.setMaximum((int) (maximum * 100f));
        slider.setMinimum((int) (minimum * 100f));
        slider.setValue((int) (currentValue0 * 100f));
        slider.setUpperValue((int) (currentValue1 * 100f));
        parameterName.setText(currentParameter.getParamNameDescription()[0]);
        parameterName.setVisible(!currentParameter.getParamNameDescription()[0].equals(""));
        textMinimum.setEditable(currentParameter.editable()[0]);
        textMaximum.setEditable(currentParameter.editable()[1]);
        this.setToolTipText(currentParameter.getParamNameDescription()[1]);

        if (chart != null) {
            final XYPlot plot = chart.getXYPlot();
            if (plot != null) {
                updatePlot();
            }
        }

        updateTextValue(currentTextValue0, currentValue0);
        updateTextValue(currentTextValue1, currentValue1);
        updateTextValue(textMinimum, minimum);
        updateTextValue(textMaximum, maximum);

        if (table != null) {
            int height_wanted = (int) getPreferredSize().getHeight();
            if (height_wanted > table.getRowHeight(tableRow))
                table.setRowHeight(tableRow, height_wanted);
        }

        revalidate();

        silenceUpdate = saveSilenceUpdate;
    }

    private void updatePlot() {

        final XYPlot plot = chart.getXYPlot();
        final NumberAxis domainAxis = new NumberAxis(null);
        domainAxis.setAutoRange(false);
        domainAxis.setTickLabelFont(new Font("Times", 0, 20));
        domainAxis.setLowerBound(minimum);
        domainAxis.setUpperBound(maximum);
        plot.setDomainAxis(domainAxis);

        if (selectionRange != null) { // if we're displaying a histogram
            selectionRange.setStartValue(currentValue0);
            selectionRange.setEndValue(currentValue1);
        }

    }

    @Override
    protected Component getRendererOrEditorComponent(JTable table0, @NonNull Object value, boolean isSelected,
            boolean hasFocus, int row, int column, boolean rendererCalled) {
        if (currentParameter != null) {
            currentParameter.removeListener(this);
        }

        currentParameter = (FloatRangeParameter) value;
        currentParameter.addGUIListener(this);

        table = table0;
        tableRow = row;

        evenTableRow = (row % 2 == 0);
        setOpaque(true);
        if (evenTableRow) {
            this.setBackground(Utils.COLOR_FOR_EVEN_ROWS);
        } else
            this.setBackground(Utils.COLOR_FOR_ODD_ROWS);
        textValueFrame.setBackground(getBackground());

        silenceUpdate = true;

        readInValuesFromParameter();

        if (currentParameter.histogram == null)
            ;// currentParameter.histogram=new XYSeries("");

        if (currentParameter.histogram instanceof XYSeries) {

            XYSeries xyData = (XYSeries) currentParameter.histogram;

            if (chart != null && chart.getXYPlot().getDataset() != null) {
                chart.getXYPlot().getDataset(0).removeChangeListener(chart.getXYPlot());
            }

            chart = ChartFactory.createXYLineChart(null, // chart title
                    null, // "Category", // domain axis label
                    null, // "Value", // range axis label
                    new XYSeriesCollection(xyData), // data
                    PlotOrientation.VERTICAL, false, // include legend
                    true, false);

        } else if (currentParameter.histogram != null) { // assume for now it's a histogram
            if (chart != null && chart.getXYPlot().getDataset() != null) {
                chart.getXYPlot().getDataset(0).removeChangeListener(chart.getXYPlot());
            }

            String plotTitle = "Histogram";
            String xaxis = "number";
            String yaxis = "value";
            PlotOrientation orientation = PlotOrientation.VERTICAL;
            boolean show = false;
            boolean toolTips = false;
            boolean urls = false;
            chart = ChartFactory.createHistogram(plotTitle, xaxis, yaxis,
                    (IntervalXYDataset) currentParameter.histogram, orientation, show, toolTips, urls);// dataset
        } else {
            if (chart != null && chart.getXYPlot().getDataset() != null) {
                chart.getXYPlot().getDataset(0).removeChangeListener(chart.getXYPlot());
            }
            chart = null;
        }

        if (currentParameter.histogram != null) {
            add(panelForHistogram, cForHistogram);
            final XYPlot plot = chart.getXYPlot();
            final NumberAxis domainAxis = new NumberAxis(null);
            domainAxis.setAutoRange(false);
            domainAxis.setTickLabelFont(new Font("Times", 0, 20));
            domainAxis.setLowerBound(minimum);
            domainAxis.setUpperBound(maximum);
            plot.setDomainAxis(domainAxis);

            final NumberAxis rangeAxis = new NumberAxis(null);
            rangeAxis.setAutoRange(true);
            rangeAxis.setVisible(false);
            plot.setRangeAxis(rangeAxis);
            chart.setBackgroundPaint(Color.white);
            chart.setPadding(new RectangleInsets(0, 0, 0, 0));

            plot.setBackgroundImage(null);
            plot.setBackgroundPaint(Color.white);
            plot.setOutlinePaint(Color.black);

            if (chartPanel == null) {
                chartPanel = new ChartPanel(chart);
                chartPanel.addMouseListener(this);
                chartPanel.addMouseMotionListener(this);
                chartPanel.setMouseWheelEnabled(true);
                chartPanel.setMouseZoomable(false);
                chartPanel.setRangeZoomable(false);

                panelForHistogram.add(chartPanel);

            } else
                chartPanel.setChart(chart);

            chartPanel.setSize(panelForHistogram.getSize());

            ((XYPlot) chart.getPlot()).getRenderer().setSeriesStroke(0, new BasicStroke(5.0f));
            selectionRange = new IntervalMarker(currentValue0, currentValue1);
            ((XYPlot) chart.getPlot()).addDomainMarker(selectionRange);
        } else {
            remove(panelForHistogram);
            setMaximumSize(new Dimension(700, 50));
            setPreferredSize(new Dimension(700, 50));
        }

        updateDisplays();

        silenceUpdate = false;
        return this;
    }

    @Override
    public Object getCellEditorValue() {
        return currentParameter;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    private boolean trackingChartMouseDrag = false;
    private int markerIntervalEdgeSelected;// 0 for left side, 1 for right side

    /**
     * Translates MouseEvent screen coordinates to coordinates in chart units
     * 
     * @param e
     *            MouseEvent containing coordinates of interest
     * @return Chart coordinates (evaluated as double)
     */
    private @Nullable Point2D getChartCoordinates(MouseEvent e) {
        if (chartPanel.getChartRenderingInfo().getChartArea().getHeight() == 0) {
            Utils.log("Cannot translate to chart coordinates", LogLevel.DEBUG);
            return null;
        }
        int mouseX = e.getX();
        int mouseY = e.getY();
        Utils.log("x = " + mouseX + ", y = " + mouseY, LogLevel.DEBUG);
        Point2D p = chartPanel.translateScreenToJava2D(new Point(mouseX, mouseY));
        XYPlot plot = (XYPlot) chart.getPlot();
        Rectangle2D plotArea = this.chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea();
        ValueAxis domainAxis = plot.getDomainAxis();
        RectangleEdge domainAxisEdge = plot.getDomainAxisEdge();
        ValueAxis rangeAxis = plot.getRangeAxis();
        RectangleEdge rangeAxisEdge = plot.getRangeAxisEdge();
        double chartX = domainAxis.java2DToValue(p.getX(), plotArea, domainAxisEdge);
        double chartY = rangeAxis.java2DToValue(p.getY(), plotArea, rangeAxisEdge);
        return new Point2D.Double(chartX, chartY);
    }

    void moveIntervalEdgeToMousePosition(MouseEvent e) {
        if (!trackingChartMouseDrag)
            return; // is this necessary?
        if (silenceUpdate)
            return;
        Point2D chartCoordinates = getChartCoordinates(e);
        if (chartCoordinates == null) {
            return;
        }

        if (markerIntervalEdgeSelected == 0) {
            // prevent the minimum and maximum from sliding past each other
            if (chartCoordinates.getX() > selectionRange.getEndValue())
                return;
            currentValue0 = (float) chartCoordinates.getX();
        } else {
            // prevent the minimum and maximum from sliding past each other
            if (chartCoordinates.getX() < selectionRange.getStartValue())
                return;
            currentValue1 = (float) chartCoordinates.getX();
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
        trackingChartMouseDrag = true;
        if (silenceUpdate)
            return;

        Point2D chartCoordinates = getChartCoordinates(e);
        if (chartCoordinates == null) {
            return;
        }
        double distanceToIntervalLeft = Math.abs(chartCoordinates.getX() - currentValue0);
        double distanceToIntervalRight = Math.abs(chartCoordinates.getX() - currentValue1);

        markerIntervalEdgeSelected = (distanceToIntervalLeft < distanceToIntervalRight) ? 0 : 1;
        moveIntervalEdgeToMousePosition(e);
        updateDisplayAndParameter(true, true);
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (!trackingChartMouseDrag)
            return; // is this necessary?
        if (silenceUpdate)
            return;
        moveIntervalEdgeToMousePosition(e);

        trackingChartMouseDrag = false;
        if (silenceUpdate)
            return;

        updateDisplayAndParameter(true, false);
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (!trackingChartMouseDrag)
            return; // is this necessary?
        if (silenceUpdate)
            return;
        moveIntervalEdgeToMousePosition(e);
        updateDisplayAndParameter(true, true);
    }

    @Override
    public void mouseMoved(MouseEvent e) {
    }

    private void updateDisplay() {
        Boolean saveSilenceUpdate = silenceUpdate;
        silenceUpdate = true;

        readInValuesFromParameter();
        updateDisplays();

        this.revalidate();
        silenceUpdate = saveSilenceUpdate;
    }

    @Override
    public void parameterValueChanged(boolean stillChanging, AbstractParameter parameterWhoseValueChanged,
            boolean keepQuiet) {
        if (!silenceUpdate) {
            updateDisplay();
        }
    }

    @Override
    public void parameterPropertiesChanged(AbstractParameter parameterWhosePropertiesChanged) {
        if (!silenceUpdate) {
            updateDisplay();
        }
    }

    @Override
    public void buttonPressed(String commandName, AbstractParameter parameter, ActionEvent event) {
    }

    @Override
    public boolean alwaysNotify() {
        return false;
    }
}