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