001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer.gpx;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.GridBagLayout;
008import java.awt.event.ActionListener;
009import java.time.ZoneId;
010import java.time.ZonedDateTime;
011import java.util.Date;
012
013import javax.swing.JCheckBox;
014import javax.swing.JPanel;
015import javax.swing.Timer;
016import javax.swing.event.ChangeListener;
017
018import org.openstreetmap.josm.gui.layer.GpxLayer;
019import org.openstreetmap.josm.gui.widgets.DateEditorWithSlider;
020import org.openstreetmap.josm.spi.preferences.Config;
021import org.openstreetmap.josm.tools.GBC;
022
023/**
024 * A panel that allows the user to input a date range he wants to filter the GPX data for.
025 */
026public class DateFilterPanel extends JPanel {
027    private final DateEditorWithSlider dateFrom = new DateEditorWithSlider(tr("From"));
028    private final DateEditorWithSlider dateTo = new DateEditorWithSlider(tr("To"));
029    private final JCheckBox noTimestampCb = new JCheckBox(tr("No timestamp"));
030    private final transient GpxLayer layer;
031
032    private transient ActionListener filterAppliedListener;
033
034    private final String prefDate0;
035    private final String prefDateMin;
036    private final String prefDateMax;
037
038    /**
039     * Create the panel to filter tracks on GPX layer @param layer by date
040     * Preferences will be stored in @param preferencePrefix
041     * If @param enabled = true, then the panel is created as active and filtering occurs immediately.
042     * @param layer GPX layer
043     * @param preferencePrefix preference prefix
044     * @param enabled panel initial enabled state
045     */
046    public DateFilterPanel(GpxLayer layer, String preferencePrefix, boolean enabled) {
047        super(new GridBagLayout());
048        prefDate0 = preferencePrefix+".showzerotimestamp";
049        prefDateMin = preferencePrefix+".mintime";
050        prefDateMax = preferencePrefix+".maxtime";
051        this.layer = layer;
052
053        final Date startTime, endTime;
054        Date[] bounds = layer.data.getMinMaxTimeForAllTracks();
055        startTime = (bounds.length == 0) ? Date.from(ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()).toInstant()) : bounds[0];
056        endTime = (bounds.length == 0) ? new Date() : bounds[1];
057
058        dateFrom.setDate(startTime);
059        dateTo.setDate(endTime);
060        dateFrom.setRange(startTime, endTime);
061        dateTo.setRange(startTime, endTime);
062
063        add(noTimestampCb, GBC.std().grid(1, 1).insets(0, 0, 5, 0));
064        add(dateFrom, GBC.std().grid(2, 1).fill(GBC.HORIZONTAL));
065        add(dateTo, GBC.eol().grid(3, 1).fill(GBC.HORIZONTAL));
066
067        setEnabled(enabled);
068
069        ChangeListener changeListener = e -> {
070            if (isEnabled()) applyFilterWithDelay();
071        };
072
073        dateFrom.addDateListener(changeListener);
074        dateTo.addDateListener(changeListener);
075        noTimestampCb.addChangeListener(changeListener);
076    }
077
078    private final Timer t = new Timer(200, e -> applyFilter());
079
080    /**
081     * Do filtering but little bit later (to reduce cpu load)
082     */
083    public void applyFilterWithDelay() {
084        if (t.isRunning()) {
085            t.restart();
086        } else {
087            t.start();
088        }
089    }
090
091    /**
092     * Applies the filter that was input by the user to the GPX track
093     */
094    public void applyFilter() {
095        t.stop();
096        filterTracksByDate();
097        if (filterAppliedListener != null)
098           filterAppliedListener.actionPerformed(null);
099    }
100
101    /**
102     * Called by other components when it is correct time to save date filtering parameters
103     */
104    public void saveInPrefs() {
105        Config.getPref().putLong(prefDateMin, dateFrom.getDate().getTime());
106        Config.getPref().putLong(prefDateMax, dateTo.getDate().getTime());
107        Config.getPref().putBoolean(prefDate0, noTimestampCb.isSelected());
108    }
109
110    /**
111     * If possible, load date ragne and "zero timestamp" option from preferences
112     * Called by other components when it is needed.
113     */
114    public void loadFromPrefs() {
115        long t1 = Config.getPref().getLong(prefDateMin, 0);
116        if (t1 != 0) dateFrom.setDate(new Date(t1));
117        long t2 = Config.getPref().getLong(prefDateMax, 0);
118        if (t2 != 0) dateTo.setDate(new Date(t2));
119        noTimestampCb.setSelected(Config.getPref().getBoolean(prefDate0, false));
120    }
121
122    /**
123     * Sets a listener that should be called after the filter was applied
124     * @param filterAppliedListener The listener to call
125     */
126    public void setFilterAppliedListener(ActionListener filterAppliedListener) {
127        this.filterAppliedListener = filterAppliedListener;
128    }
129
130    private void filterTracksByDate() {
131        Date from = dateFrom.getDate();
132        Date to = dateTo.getDate();
133        layer.filterTracksByDate(from, to, noTimestampCb.isSelected());
134    }
135
136    @Override
137    public final void setEnabled(boolean enabled) {
138        super.setEnabled(enabled);
139        for (Component c: getComponents()) {
140            c.setEnabled(enabled);
141        }
142    }
143}