001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.changeset.query;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Color;
007import java.awt.GridBagConstraints;
008import java.awt.GridBagLayout;
009import java.awt.Insets;
010import java.awt.event.ItemEvent;
011import java.awt.event.ItemListener;
012import java.time.LocalDate;
013import java.time.LocalTime;
014import java.time.ZoneId;
015import java.util.Date;
016
017import javax.swing.BorderFactory;
018import javax.swing.ButtonGroup;
019import javax.swing.JLabel;
020import javax.swing.JOptionPane;
021import javax.swing.JPanel;
022import javax.swing.JRadioButton;
023
024import org.openstreetmap.josm.gui.HelpAwareOptionPane;
025import org.openstreetmap.josm.gui.help.HelpUtil;
026import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
027import org.openstreetmap.josm.gui.widgets.JosmTextField;
028import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
029import org.openstreetmap.josm.io.ChangesetQuery;
030import org.openstreetmap.josm.spi.preferences.Config;
031
032/**
033 * This is the panel to apply a time restriction to the changeset query.
034 * @since 11326 (extracted from AdvancedChangesetQueryPanel)
035 */
036public class TimeRestrictionPanel extends JPanel implements RestrictionPanel {
037
038    private final JRadioButton rbClosedAfter = new JRadioButton();
039    private final JRadioButton rbClosedAfterAndCreatedBefore = new JRadioButton();
040    private final JosmTextField tfClosedAfterDate1 = new JosmTextField();
041    private transient DateValidator valClosedAfterDate1;
042    private final JosmTextField tfClosedAfterTime1 = new JosmTextField();
043    private transient TimeValidator valClosedAfterTime1;
044    private final JosmTextField tfClosedAfterDate2 = new JosmTextField();
045    private transient DateValidator valClosedAfterDate2;
046    private final JosmTextField tfClosedAfterTime2 = new JosmTextField();
047    private transient TimeValidator valClosedAfterTime2;
048    private final JosmTextField tfCreatedBeforeDate = new JosmTextField();
049    private transient DateValidator valCreatedBeforeDate;
050    private final JosmTextField tfCreatedBeforeTime = new JosmTextField();
051    private transient TimeValidator valCreatedBeforeTime;
052
053    /**
054     * Constructs a new {@code TimeRestrictionPanel}.
055     */
056    public TimeRestrictionPanel() {
057        build();
058    }
059
060    protected JPanel buildClosedAfterInputPanel() {
061        JPanel pnl = new JPanel(new GridBagLayout());
062        GridBagConstraints gc = new GridBagConstraints();
063        gc.fill = GridBagConstraints.HORIZONTAL;
064        gc.weightx = 0.0;
065        gc.insets = new Insets(0, 0, 0, 3);
066        pnl.add(new JLabel(tr("Date: ")), gc);
067
068        gc.gridx = 1;
069        gc.weightx = 0.7;
070        pnl.add(tfClosedAfterDate1, gc);
071        SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate1);
072        valClosedAfterDate1 = DateValidator.decorate(tfClosedAfterDate1);
073        tfClosedAfterDate1.setToolTipText(valClosedAfterDate1.getStandardTooltipTextAsHtml());
074
075        gc.gridx = 2;
076        gc.weightx = 0.0;
077        pnl.add(new JLabel(tr("Time:")), gc);
078
079        gc.gridx = 3;
080        gc.weightx = 0.3;
081        pnl.add(tfClosedAfterTime1, gc);
082        SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime1);
083        valClosedAfterTime1 = TimeValidator.decorate(tfClosedAfterTime1);
084        tfClosedAfterTime1.setToolTipText(valClosedAfterTime1.getStandardTooltipTextAsHtml());
085        return pnl;
086    }
087
088    protected JPanel buildClosedAfterAndCreatedBeforeInputPanel() {
089        JPanel pnl = new JPanel(new GridBagLayout());
090        GridBagConstraints gc = new GridBagConstraints();
091        gc.fill = GridBagConstraints.HORIZONTAL;
092        gc.weightx = 0.0;
093        gc.insets = new Insets(0, 0, 0, 3);
094        pnl.add(new JLabel(tr("Closed after - ")), gc);
095
096        gc.gridx = 1;
097        gc.fill = GridBagConstraints.HORIZONTAL;
098        gc.weightx = 0.0;
099        gc.insets = new Insets(0, 0, 0, 3);
100        pnl.add(new JLabel(tr("Date:")), gc);
101
102        gc.gridx = 2;
103        gc.weightx = 0.7;
104        pnl.add(tfClosedAfterDate2, gc);
105        SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate2);
106        valClosedAfterDate2 = DateValidator.decorate(tfClosedAfterDate2);
107        tfClosedAfterDate2.setToolTipText(valClosedAfterDate2.getStandardTooltipTextAsHtml());
108        gc.gridx = 3;
109        gc.weightx = 0.0;
110        pnl.add(new JLabel(tr("Time:")), gc);
111
112        gc.gridx = 4;
113        gc.weightx = 0.3;
114        pnl.add(tfClosedAfterTime2, gc);
115        SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime2);
116        valClosedAfterTime2 = TimeValidator.decorate(tfClosedAfterTime2);
117        tfClosedAfterTime2.setToolTipText(valClosedAfterTime2.getStandardTooltipTextAsHtml());
118
119        gc.gridy = 1;
120        gc.gridx = 0;
121        gc.fill = GridBagConstraints.HORIZONTAL;
122        gc.weightx = 0.0;
123        gc.insets = new Insets(0, 0, 0, 3);
124        pnl.add(new JLabel(tr("Created before - ")), gc);
125
126        gc.gridx = 1;
127        gc.fill = GridBagConstraints.HORIZONTAL;
128        gc.weightx = 0.0;
129        gc.insets = new Insets(0, 0, 0, 3);
130        pnl.add(new JLabel(tr("Date:")), gc);
131
132        gc.gridx = 2;
133        gc.weightx = 0.7;
134        pnl.add(tfCreatedBeforeDate, gc);
135        SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeDate);
136        valCreatedBeforeDate = DateValidator.decorate(tfCreatedBeforeDate);
137        tfCreatedBeforeDate.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml());
138
139        gc.gridx = 3;
140        gc.weightx = 0.0;
141        pnl.add(new JLabel(tr("Time:")), gc);
142
143        gc.gridx = 4;
144        gc.weightx = 0.3;
145        pnl.add(tfCreatedBeforeTime, gc);
146        SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeTime);
147        valCreatedBeforeTime = TimeValidator.decorate(tfCreatedBeforeTime);
148        tfCreatedBeforeTime.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml());
149
150        return pnl;
151    }
152
153    protected void build() {
154        setLayout(new GridBagLayout());
155        setBorder(BorderFactory.createCompoundBorder(
156                BorderFactory.createEmptyBorder(3, 3, 3, 3),
157                BorderFactory.createCompoundBorder(
158                        BorderFactory.createLineBorder(Color.GRAY),
159                        BorderFactory.createEmptyBorder(5, 5, 5, 5)
160                )
161        ));
162
163        // -- changesets closed after a specific date/time
164        //
165        GridBagConstraints gc = new GridBagConstraints();
166        gc.anchor = GridBagConstraints.NORTHWEST;
167        gc.gridx = 0;
168        gc.fill = GridBagConstraints.HORIZONTAL;
169        gc.weightx = 0.0;
170        add(rbClosedAfter, gc);
171
172        gc.gridx = 1;
173        gc.fill = GridBagConstraints.HORIZONTAL;
174        gc.weightx = 1.0;
175        add(new JMultilineLabel(tr("Only changesets closed after the following date/time")), gc);
176
177        gc.gridx = 1;
178        gc.gridy = 1;
179        gc.fill = GridBagConstraints.HORIZONTAL;
180        gc.weightx = 1.0;
181        add(buildClosedAfterInputPanel(), gc);
182
183        // -- changesets closed after a specific date/time and created before a specific date time
184        //
185        gc = new GridBagConstraints();
186        gc.anchor = GridBagConstraints.NORTHWEST;
187        gc.gridy = 2;
188        gc.gridx = 0;
189        gc.fill = GridBagConstraints.HORIZONTAL;
190        gc.weightx = 0.0;
191        add(rbClosedAfterAndCreatedBefore, gc);
192
193        gc.gridx = 1;
194        gc.fill = GridBagConstraints.HORIZONTAL;
195        gc.weightx = 1.0;
196        add(new JMultilineLabel(tr("Only changesets closed after and created before a specific date/time")), gc);
197
198        gc.gridx = 1;
199        gc.gridy = 3;
200        gc.fill = GridBagConstraints.HORIZONTAL;
201        gc.weightx = 1.0;
202        add(buildClosedAfterAndCreatedBeforeInputPanel(), gc);
203
204        ButtonGroup bg = new ButtonGroup();
205        bg.add(rbClosedAfter);
206        bg.add(rbClosedAfterAndCreatedBefore);
207
208        ItemListener restrictionChangeHandler = new TimeRestrictionChangedHandler();
209        rbClosedAfter.addItemListener(restrictionChangeHandler);
210        rbClosedAfterAndCreatedBefore.addItemListener(restrictionChangeHandler);
211
212        rbClosedAfter.setSelected(true);
213    }
214
215    /**
216     * Determines if the changeset query time information is valid.
217     * @return {@code true} if the changeset query time information is valid.
218     */
219    @Override
220    public boolean isValidChangesetQuery() {
221        if (rbClosedAfter.isSelected())
222            return valClosedAfterDate1.isValid() && valClosedAfterTime1.isValid();
223        else if (rbClosedAfterAndCreatedBefore.isSelected())
224            return valClosedAfterDate2.isValid() && valClosedAfterTime2.isValid()
225            && valCreatedBeforeDate.isValid() && valCreatedBeforeTime.isValid();
226        // should not happen
227        return true;
228    }
229
230    class TimeRestrictionChangedHandler implements ItemListener {
231        @Override
232        public void itemStateChanged(ItemEvent e) {
233            tfClosedAfterDate1.setEnabled(rbClosedAfter.isSelected());
234            tfClosedAfterTime1.setEnabled(rbClosedAfter.isSelected());
235
236            tfClosedAfterDate2.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
237            tfClosedAfterTime2.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
238            tfCreatedBeforeDate.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
239            tfCreatedBeforeTime.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
240        }
241    }
242
243    /**
244     * Initializes HMI for user input.
245     */
246    public void startUserInput() {
247        restoreFromSettings();
248    }
249
250    /**
251     * Sets the query restrictions on <code>query</code> for time based restrictions.
252     * @param query the query to fill
253     */
254    @Override
255    public void fillInQuery(ChangesetQuery query) {
256        if (!isValidChangesetQuery())
257            throw new IllegalStateException(tr("Cannot build changeset query with time based restrictions. Input is not valid."));
258        if (rbClosedAfter.isSelected()) {
259            LocalDate d1 = valClosedAfterDate1.getDate();
260            LocalTime d2 = valClosedAfterTime1.getTime();
261            final Date d3 = new Date(d1.atTime(d2).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
262            query.closedAfter(d3);
263        } else if (rbClosedAfterAndCreatedBefore.isSelected()) {
264            LocalDate d1 = valClosedAfterDate2.getDate();
265            LocalTime d2 = valClosedAfterTime2.getTime();
266            Date d3 = new Date(d1.atTime(d2).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
267
268            d1 = valCreatedBeforeDate.getDate();
269            d2 = valCreatedBeforeTime.getTime();
270            Date d4 = new Date(d1.atTime(d2).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
271
272            query.closedAfterAndCreatedBefore(d3, d4);
273        }
274    }
275
276    @Override
277    public void displayMessageIfInvalid() {
278        if (isValidChangesetQuery())
279            return;
280        HelpAwareOptionPane.showOptionDialog(
281                this,
282                tr(
283                        "<html>Please enter valid date/time values to restrict<br>"
284                        + "the query to a specific time range.</html>"
285                ),
286                tr("Invalid date/time values"),
287                JOptionPane.ERROR_MESSAGE,
288                HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidDateTimeValues")
289        );
290    }
291
292    /**
293     * Remember settings in preferences.
294     */
295    public void rememberSettings() {
296        String prefRoot = "changeset-query.advanced.time-restrictions";
297        if (rbClosedAfter.isSelected()) {
298            Config.getPref().put(prefRoot + ".query-type", "closed-after");
299        } else if (rbClosedAfterAndCreatedBefore.isSelected()) {
300            Config.getPref().put(prefRoot + ".query-type", "closed-after-created-before");
301        }
302        Config.getPref().put(prefRoot + ".closed-after.date", tfClosedAfterDate1.getText());
303        Config.getPref().put(prefRoot + ".closed-after.time", tfClosedAfterTime1.getText());
304        Config.getPref().put(prefRoot + ".closed-created.closed.date", tfClosedAfterDate2.getText());
305        Config.getPref().put(prefRoot + ".closed-created.closed.time", tfClosedAfterTime2.getText());
306        Config.getPref().put(prefRoot + ".closed-created.created.date", tfCreatedBeforeDate.getText());
307        Config.getPref().put(prefRoot + ".closed-created.created.time", tfCreatedBeforeTime.getText());
308    }
309
310    /**
311     * Restore settings from preferences.
312     */
313    public void restoreFromSettings() {
314        String prefRoot = "changeset-query.advanced.open-restrictions";
315        String v = Config.getPref().get(prefRoot + ".query-type", "closed-after");
316        rbClosedAfter.setSelected("closed-after".equals(v));
317        rbClosedAfterAndCreatedBefore.setSelected("closed-after-created-before".equals(v));
318        if (!rbClosedAfter.isSelected() && !rbClosedAfterAndCreatedBefore.isSelected()) {
319            rbClosedAfter.setSelected(true);
320        }
321        tfClosedAfterDate1.setText(Config.getPref().get(prefRoot + ".closed-after.date", ""));
322        tfClosedAfterTime1.setText(Config.getPref().get(prefRoot + ".closed-after.time", ""));
323        tfClosedAfterDate2.setText(Config.getPref().get(prefRoot + ".closed-created.closed.date", ""));
324        tfClosedAfterTime2.setText(Config.getPref().get(prefRoot + ".closed-created.closed.time", ""));
325        tfCreatedBeforeDate.setText(Config.getPref().get(prefRoot + ".closed-created.created.date", ""));
326        tfCreatedBeforeTime.setText(Config.getPref().get(prefRoot + ".closed-created.created.time", ""));
327        if (!valClosedAfterDate1.isValid()) {
328            tfClosedAfterDate1.setText("");
329        }
330        if (!valClosedAfterTime1.isValid()) {
331            tfClosedAfterTime1.setText("");
332        }
333        if (!valClosedAfterDate2.isValid()) {
334            tfClosedAfterDate2.setText("");
335        }
336        if (!valClosedAfterTime2.isValid()) {
337            tfClosedAfterTime2.setText("");
338        }
339        if (!valCreatedBeforeDate.isValid()) {
340            tfCreatedBeforeDate.setText("");
341        }
342        if (!valCreatedBeforeTime.isValid()) {
343            tfCreatedBeforeTime.setText("");
344        }
345    }
346}