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