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}