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