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