001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import java.awt.Component;
005import java.awt.Dimension;
006import java.awt.GridBagLayout;
007import java.text.DateFormat;
008import java.text.SimpleDateFormat;
009import java.util.ArrayList;
010import java.util.Date;
011import java.util.List;
012
013import javax.swing.JLabel;
014import javax.swing.JPanel;
015import javax.swing.JSlider;
016import javax.swing.JSpinner;
017import javax.swing.SpinnerDateModel;
018import javax.swing.event.ChangeListener;
019
020import org.openstreetmap.josm.tools.GBC;
021import org.openstreetmap.josm.tools.date.DateUtils;
022
023/**
024 * Widget originally created for date filtering of GPX tracks.
025 * Allows to enter the date or choose it by using slider
026 * @since 5815
027 */
028public class DateEditorWithSlider extends JPanel {
029    private final JSpinner spinner;
030    private final JSlider slider;
031    private Date dateMin;
032    private Date dateMax;
033    private static final int MAX_SLIDER = 300;
034    private boolean watchSlider = true;
035
036    private final transient List<ChangeListener> listeners = new ArrayList<>();
037
038    /**
039     * Constructs a new {@code DateEditorWithSlider} with a given label
040     * @param labelText The label to display
041     */
042    public DateEditorWithSlider(String labelText) {
043        super(new GridBagLayout());
044        spinner = new JSpinner(new SpinnerDateModel());
045        String pattern = ((SimpleDateFormat) DateUtils.getDateFormat(DateFormat.DEFAULT)).toPattern();
046        JSpinner.DateEditor timeEditor = new JSpinner.DateEditor(spinner, pattern);
047        spinner.setEditor(timeEditor);
048
049        spinner.setPreferredSize(new Dimension(spinner.getPreferredSize().width+5,
050        spinner.getPreferredSize().height));
051
052        slider = new JSlider(0, MAX_SLIDER);
053        spinner.addChangeListener(e -> {
054            int i = slider.getValue();
055            Date d = (Date) spinner.getValue();
056            int j = intFromDate(d);
057            if (i != j) {
058                watchSlider = false;
059                slider.setValue(j);
060                watchSlider = true;
061            }
062            for (ChangeListener l : listeners) {
063                l.stateChanged(e);
064            }
065        });
066        slider.addChangeListener(e -> {
067            if (!watchSlider) return;
068            Date d = (Date) spinner.getValue();
069            Date d1 = dateFromInt(slider.getValue());
070            if (!d.equals(d1)) {
071                spinner.setValue(d1);
072            }
073        });
074        add(new JLabel(labelText), GBC.std());
075        add(spinner, GBC.std().insets(10, 0, 0, 0));
076        add(slider, GBC.eol().insets(10, 0, 0, 0).fill(GBC.HORIZONTAL));
077
078        dateMin = new Date(0);
079        dateMax = new Date();
080    }
081
082    protected Date dateFromInt(int value) {
083        double k = 1.0*value/MAX_SLIDER;
084        return new Date((long) (dateMax.getTime()*k+ dateMin.getTime()*(1-k)));
085    }
086
087    protected int intFromDate(Date date) {
088        return (int) (300.0*(date.getTime()-dateMin.getTime()) /
089                (dateMax.getTime()-dateMin.getTime()));
090    }
091
092    /**
093     * Sets the date range that is available using the slider
094     * @param dateMin The min date
095     * @param dateMax The max date
096     */
097    public void setRange(Date dateMin, Date dateMax) {
098        this.dateMin = DateUtils.cloneDate(dateMin);
099        this.dateMax = DateUtils.cloneDate(dateMax);
100    }
101
102    /**
103     * Sets the slider to the given value
104     * @param date The date
105     */
106    public void setDate(Date date) {
107        spinner.setValue(DateUtils.cloneDate(date));
108    }
109
110    /**
111     * Gets the date that was selected by the user
112     * @return The date
113     */
114    public Date getDate() {
115        return DateUtils.cloneDate((Date) spinner.getValue());
116    }
117
118    /**
119     * Adds a change listener to this date editor.
120     * @param l The listener
121     */
122    public void addDateListener(ChangeListener l) {
123        listeners.add(l);
124    }
125
126    /**
127     * Removes a change listener from this date editor.
128     * @param l The listener
129     */
130    public void removeDateListener(ChangeListener l) {
131        listeners.remove(l);
132    }
133
134    @Override
135    public void setEnabled(boolean enabled) {
136        super.setEnabled(enabled);
137        for (Component c: getComponents()) {
138            c.setEnabled(enabled);
139        }
140    }
141}